Loading

整体二分

概念

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

要求:

  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;
}
posted @ 2022-07-12 08:08  kymru  阅读(57)  评论(0编辑  收藏  举报