整体二分这一算法出自许昊然同学的2013年信息学国家队论文《浅谈数据结构题的几个非经典算法》。其优点在于简短的代码量,可以在较低的代价下,简化很多需要使用复杂数据结构的问题。
不同于传统的数据结构题的解题方法,整体二分是将所有操作进行二分,然后采用分治的思想来解决问题。
问题能使用整体二分的前提:
1.满足修改操作对询问的贡献独立,修改操作之间互不影响结果。
2.题目没有强制在线。
那么前提1是什么意思呢?就是说,每一个修改,只对询问的结果产生影响,而不影响其他修改操作。
我们考虑对要执行的操作分成两部分,可以发现,在前一半操作中,所有的询问都不会收到后一半的影响,这样我们可以递归处理。后一半中的修改也是和前一半无关的,而所有查询操作则是受到前一半中所有修改操作的影响的。这样,后半部分的问题就简化为了一个先操作再询问的问题,问题也就离线化了。
而我们离线化所需要的时间开销,在许昊然论文中证明如下
所以,我们可以以一个log的开销来极大的降低我们的编码难度,将一些需要复杂的高级数据结构问题简化。
伪代码如下:
ZOJ-2112 模版题代码
#include <stdio.h>
#include <bits/stdc++.h>
using namespace std;
const int N = 50005;
const int M = 20005;
const int INF = 1e9;
struct Query{
int id,op,x,y,k;
}q[N+M],q1[N+M],q2[N+M];
int ans[N],c[N],a[N];
void Add(int i,int x){
for(;i<N;i+=(i&(-i)))
c[i]+=x;
}
int Sum(int i){
int res=0;
for(;i>0;i-=(i&(-i)))
res+=c[i];
return res;
}
void solve(int ql,int qr,int l,int r){
if(ql>qr)
return ;
if(l==r){
for(int i=ql;i<=qr;i++)
if(q[i].op==2)
ans[q[i].id]=l;
return ;
}
int m=l+r>>1,f=0,g=0;
for(int i=ql;i<=qr;i++)
if(q[i].op==1){
if(q[i].k<=m){
Add(q[i].x,q[i].id);
q1[f++]=q[i];
}else
q2[g++]=q[i];
}else if(q[i].op==2){
int cnt=Sum(q[i].y)-Sum(q[i].x-1);
if(q[i].k<=cnt)
q1[f++]=q[i];
else{
q[i].k-=cnt;
q2[g++]=q[i];
}
}
for(int i=0;i<f;i++)
if(q1[i].op==1)
Add(q1[i].x,-q1[i].id);
memcpy(q+ql,q1,f*sizeof(Query));
memcpy(q+ql+f,q2,g*sizeof(Query));
solve(ql,ql+f-1,l,m);
solve(ql+f,qr,m+1,r);
}
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,m,cnt=1,qq=1;
memset(c,0,sizeof c);
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
q[cnt++]=(Query){1,1,i,i,a[i]};
}
char opt;
int x,y,k;
for(int i=1;i<=m;i++){
scanf(" %c %d %d",&opt,&x,&y);
if(opt=='C'){
q[cnt++]=(Query){-1,1,x,x,a[x]};
q[cnt++]=(Query){1,1,x,x,y};
a[x]=y;
}else if(opt=='Q'){
scanf("%d",&k);
q[cnt++]=(Query){qq++,2,x,y,k};
}
}
solve(1,cnt-1,0,INF);
for(int i=1;i<qq;i++)
printf("%d\n",ans[i]);
}
return 0;
}
整体二分入门题:
Poj2104
Bzoj3110
Hdu5412
下面这个博客代码写的很好,我的代码就是仿照他的学习的。
大神的博客