整体二分


概念

当有很多询问,每个询问都可以通过二分解决,但是对每个询问都二分一次的时间复杂度不能接受,不妨将所有询问同时二分,是为整体二分。

要求:

  1. 允许离线。

  2. 修改之间互相独立,且具有可加性。

  3. 答案可以二分。

例题

全局第 k 小

在一个序列中多次查找第 /(k/) 小的数。

设当前询问的答案在值域 /([l, r]/) 内。令 /(m = /lfloor /frac{l + r}{2} /rfloor/),考虑对于当前值域内的每一个询问判断其答案属于 /([l, m]/) 还是 /((m, r]/)。显然地,可以使用值域树状数组维护全局小于等于 /(m/) 的值的个数,若其大于等于 /(k/) 说明在左半侧,否则在右半侧。

显然每个询问至多经过 /(/log/) 次划分得到答案,共有 /(n/) 个询问,单次划分单个询问的复杂度是 /(O(/log n)/),所以总时间复杂度是 /(O(n /log^2 n)/)

代码大概如下所示:

void solve(int l, int r, int ql, int qr)
{
	if (ql > qr) return;
	if (l == r)
	{
		for (int i = ql; i <= qr; i++) ans[q[i].idx] = l;
		return;
	}
	int cnt1 = 0, cnt2 = 0;
	int mid = (l + r) >> 1;
	for (int i = ql; i <= qr; i++)
	{
		if (query(mid) >= q[i].k) q1[++cnt1] = q[i];
		else q2[++cnt2] = q[i];
	}
	for (int i = 1; i <= cnt1; i++) q[ql + i - 1] = q1[i];
	for (int i = 1; i <= cnt2; i++) q[ql + cnt1 + i - 1] = q2[i];
	solve(l, mid, ql, ql + cnt1 - 1);
	solve(mid + 1, r, ql + cnt1, qr);
}

区间第 k 小

多次查询指定区间中第 k 小的值。

考虑将初始数组看作是 /(n/) 次分别在下标 /(1/) 到 /(n/) 上的修改,在整体二分时一同维护。每次划分时处理值在 /([l, m]/) 内的修改,即在其修改的位置加 /(1/)。用树状数组维护区间和,如果查询的区间 /([L, R]/) 的和大于等于 /(k/),说明区间第 /(k/) 小的值在 /([l, m]/) 内,划分即可。反之,记该区间再 /([l, m]/) 内的值个数为 /(x/),我们则需要查询该区间内值域在 /((m, r]/) 内的第 /(k – x/) 小值。

注意此时要先将 /(n/) 次修改压入操作数组,原因是每次划分后操作按初始顺序排列,于是需要先处理修改再询问。

容易发现划分前只关心值域在 /([l, m]/) 内的修改,所以直接将修改也随值域一同划分即可。

时间复杂度 /(O(n /log^2 n)/)

动态区间第 k 小

P2617 Dynamic Rankings

考虑把修改操作像上面一样压入数组,于是做完了。

#include <cstdio>
using namespace std;

const int maxn = 3e5 + 5;
const int maxm = 3e5 + 5;

struct item
{
    int l, r, k, idx, opt;

    item() : l(), r(), k(), idx(), opt() {}
    item(int _l, int _r, int _k, int _idx, int _opt) : l(_l), r(_r), k(_k), idx(_idx), opt(_opt) {}
} q[maxm], q1[maxm], q2[maxm];

int n, m;
int a[maxn], c[maxn];
int ans[maxm];

int lowbit(int x) { return x & (-x); }

void update(int p, int w) { for (int i = p; i <= n; i += lowbit(i)) c[i] += w; }

int query(int p)
{
    int sum = 0;
    for (int i = p; i; i -= lowbit(i)) sum += c[i];
    return sum;
}

void solve(int l, int r, int ql, int qr)
{
    if (ql > qr) return;
    if (l == r)
    {
        for (int i = ql; i <= qr; i++)
            if (q[i].opt == 2) ans[q[i].idx] = l;
        return;
    }
    int cnt1 = 0, cnt2 = 0;
    int mid = (l + r) >> 1;
    for (int i = ql; i <= qr; i++)
    {
        if (q[i].opt == 1)
        {
            if (q[i].l <= mid)
            {
                q1[++cnt1] = q[i];
                update(q[i].idx, q[i].r);
            }
            else q2[++cnt2] = q[i];
        }
        else
        {
            int t = query(q[i].r) - query(q[i].l - 1);
            if (q[i].k <= t) q1[++cnt1] = q[i];
            else
            {
                q[i].k -= t;
                q2[++cnt2] = q[i];
            }
        }
    }
    for (int i = 1; i <= cnt1; i++)
        if (q1[i].opt == 1) update(q1[i].idx, -q1[i].r);
    for (int i = 1; i <= cnt1; i++) q[ql + i - 1] = q1[i];
    for (int i = 1; i <= cnt2; i++) q[ql + cnt1 + i - 1] = q2[i];
    solve(l, mid, ql, ql + cnt1 - 1);
    solve(mid + 1, r, ql + cnt1, qr);
}

int main()
{
    int cnt = 0, cur = 0;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        q[++cnt] = item(a[i], 1, 0, i, 1);
    }
    for (int i = 1; i <= m; i++)
    {
        char opt;
        scanf(" %c ", &opt);
        if (opt == 'Q')
        {
            int l, r, k;
            scanf("%d%d%d", &l, &r, &k);
            q[++cnt] = item(l, r, k, ++cur, 2);
        }
        else
        {
            int x, y;
            scanf("%d%d", &x, &y);
            q[++cnt] = item(a[x], -1, 0, x, 1);
            q[++cnt] = item(y, 1, 0, x, 1);
            a[x] = y;
        }
    }
    solve(0, 1e9, 1, cnt);
    for (int i = 1; i <= cur; i++) printf("%d/n", ans[i]);
    return 0;
}

原创文章,作者:1402239773,如若转载,请注明出处:https://blog.ytso.com/273775.html

(0)
上一篇 2022年7月12日
下一篇 2022年7月12日

相关推荐

发表回复

登录后才能评论