CDQ分治学习笔记
有关CDQ分治求解三维偏序问题的学习与思考
以BZOJ陌上花开一题为例
首先保证在进行分治处理前,使一维有序,可以简单利用sort函数实现
在分治过程中,左区间(l,mid)对于(mid+1,r)而言不存在逆序,从而将问题转化成二维偏序问题
只需要递归处理左右区间各自问题,对于两点跨过mid,各自在左右区间的情况
可以利用树状数组、归并排序求逆序对来处理运算,就可以把左区间对右区间的贡献计算出来
(一定是左区间对右区间的贡献,因为在处理区间问题时已经保证有序,右侧对左侧而言一定具有单调性)
故一般套路为:
假设三维偏序分别为a,b,c;
1、在main函数里保证a递增。
2、在CDQ里先分治左右,传下去的时候a仍然递增,不破坏性质。
3、分治完左右两边后,需保证左右两边分别b都是递增的(a不重要)。
4、归并排序,此时左边的a肯定都小于右边的a,那么如果对于一个右边的元素,之前类似归并的操作就可以保证所有小于b的左边的元素都已经遍历过。
5、第三维只需用树状数组等数据结构维护即可
归并后,发现统计完答案后b是有序递增的了(这个时候a已经不重要了)。对于上层操作,符合"左右两边分别b是递增的"了。
复杂度:设一次分治的复杂度是f(len),则复杂度是O(f(n)logn)。一般都在分治里用树状数组,一般的复杂度就是O(nlog2n)的。
正确性:按照线段树的形态递归的CDQ分治,保证每一对三元组在第一维划分的线段树上都有且仅有一个LCA,而这一组答案就会且仅会在LCA处计算。如果在LCA下面,点对不在一次分治内自然不会计算。如果在LCA上面了,点对就在同一侧,不会互相更新。
例题 BZOJ 陌上花开
题目描述
输入格式
输出格式
样例
#include<bits/stdc++.h> #define re register using namespace std; const int L=1<<20|1; char buffer[L],*S,*TT; #define getchar() ((S==TT&&(TT=(S=buffer)+fread(buffer,1,L,stdin),S==TT))?EOF:*S++) int n,m,cnt=0,tr[1000001],ans[100001];//sum 级别 tot 等级相同的个数 struct node{int a,b,c,sum,tot;}dat[100001],cal[100001]; inline int lowbit(re int x){return x&(-x);} inline bool cmp(node a,node b){ if(a.a!=b.a) return a.a<b.a; if(a.b!=b.b) return a.b<b.b; if(a.c!=b.c) return a.c<b.c; return false; } inline bool Cmp(node a,node b){ if(a.b!=b.b) return a.b<b.b; if(a.c!=b.c) return a.c<b.c; if(a.a!=b.a) return a.a<b.a; return false; } inline int read(){ re int a=0,b=1;re char ch=getchar(); while(ch<'0'||ch>'9') b=(ch=='-')?-1:1,ch=getchar(); while(ch>='0'&&ch<='9') a=(a<<3)+(a<<1)+(ch^48),ch=getchar(); return a*b; } inline void add(re int x,re int y){ for(;x<=m;x+=lowbit(x)) tr[x]+=y; } inline int getsum(re int x){ re int res=0; for(;x;x-=lowbit(x)) res+=tr[x]; return res; } inline void cdq(re int l,re int r){ if(l==r){cal[l].sum+=cal[r].tot-1;return ;} re int mid=(l+r)>>1; cdq(l,mid),cdq(mid+1,r); sort(cal+l,cal+mid+1,Cmp); sort(cal+mid+1,cal+r+1,Cmp); re int p=l; for(re int i=mid+1;i<=r;i++){ while(p<=mid&&cal[p].b<=cal[i].b) add(cal[p].c,cal[p].tot),p++; cal[i].sum+=getsum(cal[i].c); } for(re int i=l;i<p;i++) add(cal[i].c,-cal[i].tot); } signed main(){ n=read(),m=read(); for(re int i=1;i<=n;i++) dat[i].a=read(),dat[i].b=read(),dat[i].c=read(),dat[i].sum=1; sort(dat+1,dat+n+1,cmp); cal[++cnt]=dat[1],cal[cnt].tot=1; for(re int i=2;i<=n;i++){ if(dat[i].a==dat[i-1].a&&dat[i].b==dat[i-1].b&&dat[i].c==dat[i-1].c) cal[cnt].tot++; else cal[++cnt]=dat[i],cal[cnt].tot=1; } cdq(1,cnt); sort(cal+1,cal+cnt+1,cmp); for(re int i=1;i<=cnt;i++) ans[cal[i].sum]+=cal[i].tot; for(re int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }