CDQ分治简介(洛谷P3810、BZOJ3262)
2018-02-12 15:23
295 查看
%%%陈丹琦巨佬
普通的分治中,左半个区间是不影响右半个区间的,做完当前区间直接递归。而CDQ分治一般会经过以下三个步骤:
1.将当前区间分成两半(废话)
2.考虑并计算左区间的所有操作对右区间的影响。
3.递归求解。
给出许多个有三个属性(x,y,z)(x,y,z)的元素,对于每个元素,求≤它的元素个数。(a≤b:aa≤b:a的三个属性值分别小于bb)
当只有两个属性时,把两个属性按照第一、二关键字从小到大排序,其实就是逆序对问题,可以用树状数组求解。
而有三个属性时,如果仍然用数据结构维护,同样把三个属性按照第一、二、三关键字排序,用树状数组维护第二个关键字,平衡树维护第三个关键字。即树状数组套平衡树。代码又臭又长。
我们考虑CDQ分治。把每个点看成一个数,所有点看成一个序列。先按三个关键字排完序后,对于一个分治的区间[l,r][l,r],再以yy为第一关键字,编号为第二关键字进行排序。这时yy不产生影响,直接用树状数组维护zz即可。把左区间的影响加上并计算答案,再撤销左区间的影响。最后递归求解。
算法用途
当碰到一些动态的题目时,常常需要用到高级数据结构来维护,代码又臭又长。而在某些情况下,CDQ分治可以代替这些高级数据结构,转动态为静态来处理,使代码复杂度大大降低。算法实现
基本步骤
看到这个名称,就知道肯定是用分治的思想来解决了。普通的分治中,左半个区间是不影响右半个区间的,做完当前区间直接递归。而CDQ分治一般会经过以下三个步骤:
1.将当前区间分成两半(废话)
2.考虑并计算左区间的所有操作对右区间的影响。
3.递归求解。
经典问题
CDQ分治中最经典的问题就是三维偏序问题:给出许多个有三个属性(x,y,z)(x,y,z)的元素,对于每个元素,求≤它的元素个数。(a≤b:aa≤b:a的三个属性值分别小于bb)
当只有两个属性时,把两个属性按照第一、二关键字从小到大排序,其实就是逆序对问题,可以用树状数组求解。
而有三个属性时,如果仍然用数据结构维护,同样把三个属性按照第一、二、三关键字排序,用树状数组维护第二个关键字,平衡树维护第三个关键字。即树状数组套平衡树。代码又臭又长。
我们考虑CDQ分治。把每个点看成一个数,所有点看成一个序列。先按三个关键字排完序后,对于一个分治的区间[l,r][l,r],再以yy为第一关键字,编号为第二关键字进行排序。这时yy不产生影响,直接用树状数组维护zz即可。把左区间的影响加上并计算答案,再撤销左区间的影响。最后递归求解。
模板
以洛谷P3810(BZOJ3262)为例:#include<cctype> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define F inline using namespace std; struct px{ int x,y,z,id; bool operator == (const px &a) const{ return a.x==x&&a.y==y&&a.z==z; } }q ,tem ; int n,k,t[N<<1],ans[N<<1],ti[N<<1]; F char readc(){ static char buf[100000],*l=buf,*r=buf; if (l==r) r=(l=buf)+fread(buf,1,100000,stdin); if (l==r) return EOF; return *l++; } F int _read(){ int x=0,f=1; char ch=readc(); while (!isdigit(ch)) { if (ch=='-') f=-1; ch=readc(); } while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc(); return x*f; } F void writec(int x){ if (x>9) writec(x/10); putchar(x%10+48); } F void _write(int x){ writec(x),putchar('\n'); } //以上为IO优化 #define lbt(x) (x&(-x)) F void nsrt(int x,int p){ while (x<=k) t[x]+=p,x+=lbt(x); } F int srch(int l,int r){ int x,sum=0; l--; x=l; while (x) sum-=t[x],x-=lbt(x); x=r; while (x) sum+=t[x],x-=lbt(x); return sum; } //以上为树状数组单点修改,区间查询 F bool cmp1(px a,px b){ return a.y<b.y||(a.y==b.y&&a.id<b.id); } F bool cmp2(px a,px b){ bool x=(a.x==b.x),y=(a.y==b.y),z=(a.z==b.z); return a.x<b.x||(x&a.y<b.y)||(x&y&a.z<b.z)||(x&y&z&a.id<b.id); } F void cdq(int l,int r){ if (l==r) return; int mid=l+r>>1; for (int i=l;i<=mid;i++) tem[i]=q[i],tem[i].id=0; //把左区间的id赋成0,这样就可以保证左区间的影响 for (int i=mid+1;i<=r;i++) tem[i]=q[i]; sort(tem+l,tem+r+1,cmp1); for (int i=l;i<=r;i++) if (!tem[i].id) nsrt(tem[i].z,1);//加上影响 else ans[tem[i].id]+=srch(1,tem[i].z);//计算 for (int i=l;i<=r;i++) if (!tem[i].id) nsrt(tem[i].z,-1);//消去影响 cdq(l,mid),cdq(mid+1,r);//递归 } //以上为CDQ分治 int main(){ n=_read(),k=_read(); for (int i=1;i<=n;i++) q[i].x=_read(),q[i].y=_read(),q[i].z=_read(),q[i].id=i; sort(q+1,q+n+1,cmp2); for (int i=n-1,sum=0;i;i--) ans[q[i].id]+=(q[i]==q[i+1]?++sum:sum=0); cdq(1,n); for (int i=1;i<=n;i++) t[ans[i]]++; for (int i=0;i<n;i++) _write(t[i]); return 0; }
相关文章推荐
- [bzoj] 3263 陌上花开 洛谷 P3810 三维偏序|| CDQ分治 && CDQ分治讲解
- 【BZOJ 3262】 3262: 陌上花开 (CDQ分治)
- cdq分治入门--BZOJ3262: 陌上花开
- BZOJ 3262 陌上花开 - CDQ分治
- 【BZOJ3262】陌上花开(CDQ分治)
- [CDQ分治] [树状数组] [BZOJ3262] 陌上花开
- [cdq分治] bzoj3262: 陌上花开
- 【bzoj 3262】陌上花开(CDQ分治)
- bzoj 3262: 陌上花开(cdq分治)
- BZOJ - 3262 陌上花开 CDQ分治 三维偏序
- BZOJ 3262 陌上花开 树套树 (CDQ分治)
- bzoj 3262: 陌上花开 -- CDQ分治
- BZOJ 3262 cdq分治 OR 树套树
- bzoj 3262(cdq分治)
- BZOJ 3262 cdq分治 OR 树套树
- BZOJ 3262 陌上花开 CDQ分治
- 【BZOJ3262】陌上花开 cdq分治
- bzoj 3262(cdq分治)
- bzoj 3262: 陌上花开 (cdq分治)
- bzoj 3262: 陌上花开(cdq分治)