例题:动态区间第k小
先上代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #define mid ((l+r)>>1) #define lowbit(x) (x&-x) #define N 50005 #define M 20005 using namespace std; const int inf=1000000007; struct zero{ int x,y,k,op,id; }q[N+M],q1[N+M],q2[N+M]; int tree[N],tot=0,n,m,ans[M]; int T,x,y,k,cnt=0; int input[N]; char c; void add(int a,int k){for(;a<=max(n,m);a+=lowbit(a))tree[a]+=k;} int ask(int a){int ans=0;for(;a>0;a-=lowbit(a))ans+=tree[a];return ans;} 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==0)ans[q[i].id]=l; return; } int t1=0,t2=0; for(int i=ql;i<=qr;i++){ if(q[i].op){ if(q[i].x<=mid)add(q[i].id,q[i].op),q1[++t1]=q[i]; else q2[++t2]=q[i]; } else{ int sum=ask(q[i].y)-ask(q[i].x-1); if(sum>=q[i].k)q1[++t1]=q[i]; else q[i].k-=sum,q2[++t2]=q[i]; } } for(int i=1;i<=t1;i++)if(q1[i].op)add(q1[i].id,-q1[i].op); for(int i=1;i<=t1;i++)q[i+ql-1]=q1[i]; for(int i=1;i<=t2;i++)q[i+ql+t1-1]=q2[i]; solve(ql,t1+ql-1,l,mid); solve(t1+ql,qr,mid+1,r); } int main(){ scanf("%d",&T); while(T--){ cnt=tot=0; memset(q,0,sizeof q); memset(tree,0,sizeof tree); memset(q1,0,sizeof q1); memset(q2,0,sizeof q2); memset(ans,0,sizeof ans); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",input+i); q[++tot].x=input[i],q[tot].op=1,q[tot].id=i; } for(int i=1;i<=m;i++){ cin>>c; if(c=='C'){ scanf("%d%d",&k,&x); q[++tot].x=input[k],q[tot].op=-1,q[tot].id=k; input[k]=x; q[++tot].x=input[k],q[tot].op=1,q[tot].id=k; } else { scanf("%d%d%d",&x,&y,&k); q[++tot].x=x,q[tot].y=y,q[tot].k=k,q[tot].op=0,q[tot].id=++cnt; } } solve(1,tot,-inf,inf); for(int i=1;i<=cnt;i++)printf("%d\n",ans[i]); } }
解释一下struct zero:
对于修改函数:x表示修改的值,op表示该操作是修改还是查寻,id为该操作作用点在数组中的位置
对于查寻函数:x,y表示查寻区间,k表示在这区间中查找第k小,op和id意义同上
整体二分的主要思路就是把一段操作(修改和查寻)序列分成两段,分治操作
solve(ql,t1+ql-1,l,mid); solve(t1+ql,qr,mid+1,r);
就是这样
那么怎么分段呢
我们来看solve(ql,qr,l,r)函数,
这里ql,qr,l,r的意义就是序列中ql到qr的所有询问,它的答案在l到r之间
算了我在代码里讲吧,那么请继续看代码
void solve(int ql,int qr,int l,int r){ if(ql>qr)return;//特判防RE if(l==r){//l==r代表答案已经确定了,那么把ql到qr中所有询问答案赋成l for(int i=ql;i<=qr;i++) if(q[i].op==0)ans[q[i].id]=l; return; } int t1=0,t2=0; for(int i=ql;i<=qr;i++){ if(q[i].op){ if(q[i].x<=mid)add(q[i].id,q[i].op),q1[++t1]=q[i];//q[i]/x<=mid代表这个修改对ql到qr区间内的查寻有影响,我们把它加到树状数组里,又因为答案在l~mid里的询问又会受它影响
//所以要把它丢进q1继续发挥作用.至于答案在mid+1~r里的询问,虽然受它影响,但影响为定值,所以我们用树状数组计算出这个值即可消去影响 else q2[++t2]=q[i];//对答案在l~mid里的询问没影响,加入q2影响后半段 } else{ int sum=ask(q[i].y)-ask(q[i].x-1); if(sum>=q[i].k)q1[++t1]=q[i];//同理,sum>=q[i].k表示q[i]的答案在l~mid内,查寻l~mid else q[i].k-=sum,q2[++t2]=q[i];//否则,减去先前的影响(定值)消去影响 } } for(int i=1;i<=t1;i++)if(q1[i].op)add(q1[i].id,-q1[i].op);//重置树状数组 for(int i=1;i<=t1;i++)q[i+ql-1]=q1[i]; for(int i=1;i<=t2;i++)q[i+ql+t1-1]=q2[i]; solve(ql,t1+ql-1,l,mid); solve(t1+ql,qr,mid+1,r);//分治 }
然后注意main里把一个点变成x的操作要拆成两个操作实现
if(c=='C'){ scanf("%d%d",&k,&x); q[++tot].x=input[k],q[tot].op=-1,q[tot].id=k; input[k]=x; q[++tot].x=input[k],q[tot].op=1,q[tot].id=k; }