整体二分
整体二分是一个求解区间第K小(大)非常优秀的算法,但是要求离线处理,对于所有询问做整体的二分答案操作。相较于主席树 ,树套树,整体二分( 应该 )更加优秀。
我用主席树与整体二分写,并没有发现在时间上整体二分快多少,我自己算时间复杂度也觉得两者差不多(也可能我写的太丑了),但是空间上整体二分当然非常占优
上问题: (对于整体二分带修改的其实和不带修改其实差不多,后面会说)
给定一个长度为n数列,进行m次询问,每次询问 l 到 r 的 第 k 小的数是多少。
首先,我们考虑怎样对一个询问进行二分答案求解,从-inf到inf不断二分答案,每一次二分后,在数列中求出所询问区间内小于等于mid的个数num,如果num>=k,说明在这num个小于等于mid的数当中包含所求答案,反之答案则在区间大于mid的数当中,这时,更新数列为原数列中小于等于mid数,或者其他大于mid的数,便于下次二分答案。
而求解区间内有多少个小于等于mid的数时,单次询问当然直接遍历,但是在整体二分时,面对许多询问,我们可以用树状数组,把数列中小于等于mid的数的位置下标标记,然后sum(r) - sum(l-1)求出此区间小于等于mid的数。为了方便大家理解,我下面对于单组询问也做树状数组的处理
整体二分即是把这种二分答案操作做出整体把握,对所有询问二分,所以一定要理解这种求解方式
下面我给出模拟过程:
首先一个长度为7数列: 6(1) 2(2) 5(3) 3(4) 1(5) 9(6) 2(7) 询问 2 到 6 的第 3 小数,括号里是位置下标
an:a1 a2 …an 数列
l :二分答案的左区间
r :二分答案的右区间
L:询问区间的左区间
R:询问区间的右区间
K: 第几小
因为数列中最小为1,最大为9,我令 l = 1 , r = 9;
首先初始状态:
an: 6(1) 2(2) 5(3) 3(4) 1(5) 9(6) 2(7)
l = 1 , r = 9 , L = 2 , R = 6 , K = 3
1:
mid = (l+r)>>1 = 5;
插入 2(2) 5(3) 3(4) 1(5) 2(7) 的位置到树状数组。
查询 num = sum® - sum(L-1) = 4;
发现 num >= k , 说明答案小于等于5;
所以 令 r = mid;
并更新数列为: 2(2) 5(3) 3(4) 1(5) 2(7)
2:
mid = (l+r)>>1 = 3;
插入 2(2) 3(4) 1(5) 2(7) 的位置到树状数组。
查询 num = sum® - sum(L-1) = 3 ;
发现 num >= k , 说明答案小于等于3;
所以 令 r = mid;
并更新数列为: 2(2) 3(4) 1(5) 2(7)
3:
mid = (l+r)>>1 = 2;
插入 2(2) 1(5) 2(7) 的位置到树状数组。
查询 num = sum® - sum(L-1) = 2 ;
发现 num < k , 说明答案大于3;
所以 令 l = mid+1;K = K - 2;
并更新数列为: 3(4)
4:
l = r = 3 , 所以答案为3;
整体二分则是在上述的基础上,把询问打包存下来,每次先遍历数列,插入小于等于mid的数的位置,再处理所有询问,如果该询问的答案小于等于mid,就对应到小于等于的mid的新数列里,反之对应另一边,这样不断整体分划,直到l==r时,此时对应的某些询问,答案就都是l。
还是上代码,理解了上面,代码多看几遍应该就OK了
区间第K小
#include<iostream>
using namespace std;
const int maxn=1e5+5 , maxm=1e4 , inf=1e9+7;
int n,m,cnt;
int ans[maxn];
struct node{
int x,y,k;
int pos,tp;
};
node z[maxn+maxm],le[maxn+maxm],ri[maxn+maxm];
int sum[maxn];
int lowbit(int x){
return x&(-x); }
void add(int x,int d){
while(x<=n){
sum[x] += d; x += lowbit(x);
}
}
int query(int x){
int ans = 0;
while(x>0){
ans += sum[x]; x -= lowbit(x);
}
return ans;
}
void CDQ(int l,int r,int L,int R)
{
if(l>r||L>R) return;
if(l==r){
for(int i=L;i<=R;i++)
if(z[i].tp) ans[z[i].pos