CDQ 学习笔记
CDQ分治
CDQ(陈丹琦)分治是一种特殊的分治方法。
它只能处理非强制在线的问题。
CDQ分治在维护一些动态的凸包、半平面交问题也有一定应用,然而本渣渣并不会。
CDQ分治基于时间分治,整体二分基于答案分治。
步骤
1:将操作按照某个关键字排序
2;算出[L,mid]对[mid+1,R]的贡献
3;递归处理[L,mid]和[mid+1,R]
注:这里的区间指的是操作区间。
题目必须满足“修改独立,允许离线”两个条件。
这样的话我们把操作区间二分
会发现后一半的修改操作对前一半的询问操作不会产生影响
前后两个区间的联系只是前一半的修改操作会影响后一半的询问操作。
这个东西我们是可以事先算出来的:对于在满足某种限制下的答案贡献进行合并
用CDQ分治可以解决多维偏序问题
例题
一道简单的题目(来自YJY学长)
你有一个长度为N的棋盘,每个格子内有一个整数
两种操作:
1 x A 将格子x里的数字加上A
2 x y输出x y 这个区间内的数字和
1<=N<=100000,操作数不超过10000个,内存限制128M。
是不是很水啊。。。
几个做法
- 暴力O(NM)
- 线段树单点修改区间查询或树状数组维护差分数组O((n+m)logn)
- 分块修改O(1),查询O(q√N)
- 那么CDQ分治怎么做?
我们对x升序排序,然后按照时间分治,分治的时候记录一个前缀和
我们要保证贡献的计算不重不漏,根据上面的思路,是不是非常简单啊。。。
#include<algorithm> #include<cstdio> #define MAXN 100001 using namespace std; int s[MAXN],n,m,tot,t,sum,ans[MAXN]; struct data{int x,k,t,o,z,belong;}q[MAXN*2],tmp[MAXN*2]; int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar(); return x*f; } bool cmp(const data &x,const data &y){ if(x.x!=y.x) return x.x<y.x; else return x.k<y.k; } void slove(int l,int r){ if(l==r) return ; int mid=(l+r)>>1,ll=l,rr=mid+1; for(int i=l;i<=r;i++){ if(q[i].k==1&&q[i].t<=mid) sum+=q[i].z; else if(q[i].k==2&&q[i].t>mid) ans[q[i].belong]+=sum*q[i].z; } for(int i=l;i<=r;i++){ if(q[i].t<=mid) tmp[ll++]=q[i]; else tmp[rr++]=q[i]; } sum=0; for(int i=l;i<=r;i++) q[i]=tmp[i]; slove(l,mid),slove(mid+1,r); return ; } int main() { int x,y,z; n=read(); for(int i=1;i<=n;i++) x=read(),q[++tot].x=i,q[tot].k=1,q[tot].z=x,q[tot].t=tot; m=read(); for(int i=1;i<=m;i++){ z=read(),x=read(),y=read(); if(z&1) q[++tot].x=x,q[tot].k=1,q[tot].z=y,q[tot].t=tot; else{ q[++tot].x=x-1,q[tot].k=2,q[tot].z=-1,q[tot].t=tot,q[tot].belong=++t; q[++tot].x=y,q[tot].k=2,q[tot].z=1,q[tot].t=tot,q[tot].belong=t; } } sort(q+1,q+tot+1,cmp); slove(1,tot); for(int i=1;i<=t;i++) printf("%d\n",ans[i]); return 0; }
BZOJ 2683简单题