整体二分
上次的 C D Q CDQ CDQ 分治到现在还没写,现在来写整体二分啦qwq。
简单的介绍
wss:整体二分顾名思义就是他是整体的二分。
具体来说,如果满足这几个条件,我们可以考虑这道题用整体二分来做:
- 多组询问
- 每一个询问可以用二分答案解决
- 答案可以分批次贡献
这个第三点可能有点抽象,等会儿用具体的题目来理解可能会更好一些,所以在此不做过多解释。
看看一道简单的题目吧
传送门:luogu1533 可怜的狗狗
题目大意
给你一个序列(每个数都不相等),每次给你一个询问,问你区间第 k k k 小。
分析
这不是板子吗, 直接主席树(平衡树也行qwq)走起。
我们今天是来学整体二分的,所以先缓一下,来看看整体二分的分析思路。
首先,我们来看看这个问题满不满足上面的三个条件。首先第一个条件,显然满足(应该不用解释吧)。然后是第二个条件,也很显然满足。我们考虑这样二分答案,在值域上二分出一个 m i d mid mid。 c h e c k check check 的时候就扫一遍数组,扫出现在是小于 m i d mid mid 的数的个数 r e s res res。如果 r e s = k − 1 res = k - 1 res=k−1,那么 a n s = m i d ans = mid ans=mid,就直接出来了。如果 r e s < k − 1 res < k - 1 res<k−1 那么 l = m i d l = mid l=mid,如果 r e s > k − 1 res > k - 1 res>k−1,那么 r = m i d r = mid r=mid。这样做的话每一次询问的复杂度显然就是 O ( n log n ) O(n\log n) O(nlogn) 的,所以总的复杂度是 O ( n m log n ) O(nm\log n) O(nmlogn) 的。
然后是第三个条件,答案可以分批次贡献,具体怎么理解呢,我们来考虑二分的过程。
首先我们找到一个 m i d mid mid,发现这个数组中小于 m i d mid mid 的数有 r e s < k − 1 res < k - 1 res<k−1 个,那么说明我们要求的答案要比 m i d mid mid 大。所以我们把 l = m i d l = mid l=mid,继续二分。这是二分答案中非常正常的一步,对吧,如果还是正常的二分答案,我们会重复这个过程直到找到 r e s = k − 1 res = k - 1 res=k−1 为止。但是我们考虑另一种做法:
我们现在有 r e s res res 个数小于 m i d mid mid,而我们下次二分出来的数 m i d ′ mid' mid′ 有 r e s ′ res' res′ 个数小于 m i d ′ mid' mid′,那么就有 r e s ′ o p t k − 1 res' \; opt \; k - 1 res′optk−1,那么我们在式子的两边同时减去 r e s res res,也就是 r e s ′ − r e s o p t k − 1 − r e s res' - res \;opt\; k - 1 - res res′−resoptk−1−res。
现在左边式子的含义就是小于 m i d ′ mid' mid′ 的数的个数减去小于 m i d mid mid 的数的个数,也就是满足 a i ∈ [ m i d , m i d ′ ) ∧ i ∈ [ l , r ] a_i \in[mid, mid') \land i \in[l, r] ai∈[mid,mid′)∧i∈[l,r] 的数的个数。然后这个数要和我们最初要比较的数 k − 1 k - 1 k−1,减去我们中间求出的数 r e s res res 得到的 k − 1 − r e s k - 1 - res k−1−res 比较。这就是答案分批次贡献的含义,这里需要注意,我们说的分批次贡献仅仅指左侧对右侧的贡献,你想象一下如果我们第一次的 m i d mid mid 的 r e s res res 大于 k − 1 k - 1 k−1 那么就不能进行上述的操作,所以只考虑左对右的贡献。
所以这道题就可以用整体二分来做 好痛苦的过程qwq。
首先因为题目没说权值的范围啊,所以我们给它离散化一下。然后我们把所有的询问离线下来,存到一个数组里面,就像这样:
左边每一个点都是一个询问,对于第 i i i 个询问会包含 l i , r i l_i, r_i li,ri 和 k i k_i ki,然后我们再在值域上进行二分,现在我们二分出了一个 m i d mid mid,然后我们为了快速的查询有多少数小于 m i d mid mid,我们把原数组中小于 m i d mid mid 的数加入一个树状数组里面(值域加一),这样我们就能快速查询区间 [ l , r ] [l, r] [l,r] 中有多少数小于 m i d mid mid 了。
然后我们考虑对于每一个询问我们进行一下查询,发现现在有 r e s i res_i resi 个数满足 a i ∈ [ 1 , m i d ∧ i ∈ [ l , r ] ) a_i \in [1, mid \land i \in [l, r]) ai∈[1,mid∧i∈[l,r])。然后拿 r e s i res_i resi 和 k i − 1 k_i - 1 ki−1 进行一个比较。
然后这就是整体二分比较关键的一个步骤了,比较的结果可以分成两类:
- r e s i ≥ k i + 1 res_i \geq k_i + 1 resi≥ki+1,对于这一部分询问,我们把它们分类为第一类。
- r e s i < k i + 1 res_i < k_i + 1 resi<ki+1,对于这一部分询问,我们把它们分类为第二类。
然后我们对于这两类询问分别做以下处理:
- 第一类询问,把它放在 “左边”,不管它。
- 第二类询问,把它放在 “右边”,并且 k i − = r e s i k_i -= res_i ki−=resi。
这样做的目的其实就和上面我们在分析第三个条件答案分批次贡献的意图是一样的了,这样我们继续向下分,继续处理下去,直到最后到每一类只剩一个询问,我们就可以返回我们弄出来的答案了(不太理解的可以类比归并排序)。
代码
#include<bits/stdc++.h>
using namespace std;
#define in read()
#define MAXN 300300
#define MAXM 50050
inline int read(){
int x = 0; char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9'){
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int a[MAXN] = { 0 };
int b[MAXN] = { 0 };
int n = 0; int m = 0;
int id[MAXN] = { 0 };
int q1[MAXN] = { 0 };
int q2[MAXN] = { 0 };
int pos[MAXN] = { 0 };
int ans[MAXN] = { 0 };
struct Tque{
int k;
int l, r;
}q[MAXM];
int c[MAXN << 2] = { 0 };
inline int lowbit(int x) { return x & -x; }
void add(int x, int y) { for(; x <= n; x += lowbit(x)) c[x] += y; }
int query(int x){
int ans = 0;
for(; x; x -= lowbit(x)) ans += c[x];
return ans;
}
void solve(int l, int r, int ql, int qr){
if(l == r){
for(int i = ql; i <= qr; i++) ans[id[i]] = l;
return;
}
int mid = (l + r) >> 1; int cnt1 = 0, cnt2 = 0, res;
for(int i = l; i <= mid; i++) add(pos[i], 1);
for(int i = ql; i <= qr; i++){
res = query(q[id[i]].r) - query(q[id[i]].l - 1);
if(res >= q[id[i]].k) q1[++cnt1] = id[i];
else q[id[i]].k -= res, q2[++cnt2] = id[i];
}
for(int i = l; i <= mid; i++) add(pos[i], -1); // 撤销
for(int i = 1; i <= cnt1; i++) id[i + ql - 1] = q1[i];
for(int i = 1; i <= cnt2; i++) id[i + ql - 1 + cnt1] = q2[i];
solve(l, mid, ql, cnt1 + ql - 1), solve(mid + 1, r, cnt1 + ql, qr);
}
int main(){
n = in; m = in;
for(int i = 1; i <= n; i++) b[i] = a[i] = in, id[i] = i;
for(int i = 1; i <= m; i++) q[i].l = in, q[i].r = in, q[i].k = in;
sort(b + 1, b + n + 1);
int k = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++)
a[i] = lower_bound(b + 1, b + k + 1, a[i]) - b, pos[a[i]] = i;
solve(1, n, 1, m);
for(int i = 1; i <= m; i++) cout << b[ans[i]] << '\n';
return 0;
}