整体二分
概念
当有很多询问,每个询问都可以通过二分解决,但是对每个询问都二分一次的时间复杂度不能接受,不妨将所有询问同时二分,是为整体二分。
要求:
-
允许离线。
-
修改之间互相独立,且具有可加性。
-
答案可以二分。
例题
全局第 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 小
考虑把修改操作像上面一样压入数组,于是做完了。
#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;
}