多方查找找到了2008年陈丹琪引入CDQ分治的 从《Cash》谈一类分治算法的应用.doc ,CDQ分治的名字由来也是她.
什么叫CDQ分治呢?来看一道二维数点题p1357.
看了一眼题,我会树状数组!
现在拿它来引入CDQ分治.先全部按照x排序,对于区间[l,r]内的贡献都可以分四步进行.
1.算区间[l,mid]的贡献并把[l,mid]排序
2.算区间[mid+1,r]的贡献并把[mid+1,r]排序
3.算[l,mid]对于[mid+1,r]的贡献
4.把[l,r]排序.
删去算贡献的部分,是不是很像归并排序?基于归并排序的思想,[l,mid]和[mid+1,r]内部是有序的,我们可以O(r-l)的进行[l,r]的排序以及算贡献的过程.
那啥,为了防止有些人不懂,先放一个nlog^2n的算法
bool Orz(node a,node b) { return a.x<b.x; } void CDQ(int l,int r) { if(l==r) return ;//自己对自己当然没贡献了 int mid=(l+r)/2; CDQ(l,mid); CDQ(mid+1,r); //简陋的树状数组实现贡献统计 for(int i=l;i<=mid;i++)//怎么还用树状数组啊? add(o[i].y,1); for(int i=mid+1;i<=r;i++) ans[o[i].i]+=ask(o[i].y);//算贡献 for(int i=l;i<=mid;i++)//怎么又减回去了啊? add(o[i].y,-1); sort(o+l,o+r+1);//简陋的sort排序 } int main() { freopen("123.in","r",stdin); n=read(); for(i=1;i<=n;i++) { o[i].x=read(); o[i].y=read(); o[i].i=i;//记录原始位置 } sort(o+1,o+1+n,Orz); CDQ(1,n);//调用分治 for(i=1;i<=n;i++) cout<<ans[i]<<endl; }
上面的简陋的算法的log是完全可以省掉的,就像归并排序统计逆序对一样,那个树状数组实现的统计贡献完全可以O(l-r)实现.
不会有人连归并排序都不会吧?
struct node { int x,y; int i; }o[60010],temp[60010]; int ans[60010]; int i,n; inline bool Orz(node a,node b) { return a.x==b.x?a.y<b.y:a.x<b.x; } inline void CDQ(int l,int r) { if(l==r) return ;//自己对自己当然没贡献了 int mid=(l+r)/2; CDQ(l,mid); CDQ(mid+1,r); int a=l,b=mid+1,tot=l; for(;a<=mid&&b<=r;tot++)//合并区间并统计答案 { if(o[a].y<=o[b].y) { temp[tot]=o[a]; a++; } else { ans[o[b].i]+=a-l;//统计答案 temp[tot]=o[b]; b++; } } while(a<=mid) { temp[tot]=o[a]; a++;tot++; } while(b<=r) { ans[o[b].i]+=a-l;//等价于mid+l-1 temp[tot]=o[b]; b++;tot++; } for(int i=l;i<=r;i++) o[i]=temp[i]; } int main() { n=read(); for(i=1;i<=n;i++) { o[i].x=read(); o[i].y=read(); o[i].i=i;//记录原始位置 } sort(o+1,o+1+n,Orz); CDQ(1,n);//调用分治 for(i=1;i<=n;i++) { write(ans[i]); putchar(10); } }
时间比较:
我的CDQ
帆神的树状数组