CDQ分治是一个好东西,一直听着dalao们说因此就去学了下。git
CDQ分治是咱们处理各种问题的重要武器。它的优点在于能够顶替复杂的高级数据结构,并且常数比较小;缺点在于必须离线操做。 ——by __stdcall数组
其实CDQ分治名字听上去很高大上,其实和通常的分治没有特别大的区别,其大致流程以下:数据结构
这里特别注意CDQ分治与通常分治的区别:普通分治在合并两个子问题的过程当中,左右区间内的问题不会互相影响。spa
咱们从一道模板题来看看CDQ的具体实现:Luogu P3810指针
首先考虑经典的二维偏序:逆序对code
这个鬼东西不是就一个归并排序or权值树状数组的事情么排序
咱们想一下归并排序的原理,在归并的过程当中(数组已经有序),那么我左边的而且坐标大于右边的坐标个数其实就是逆序对个数。递归
所以这也算是个简单的CDQ吧get
如今咱们考虑三维偏序,咱们考虑先对数组整体排个序,这样在操做的过程当中总有\(a_i\le a_j(i<j)\)(即便咱们将区间一分为二那么右边的数的\(a_i\)始终大于左边。it
而后对于第二维\(y_i\),咱们考虑一下处理方法。
假设如今处理区间\([l,r]\),而此前咱们已经经过递归处理好了\([l,mid]\)和\([mid+1,r]\)的答案。
那咱们把\([l,mid]\)和\([mid+1,r]\)分别按\(y_i\)排个序,这样第二维也有了上面的性质。
再考虑怎么计算左边和右边的偏序关系,咱们能够维护两个指针\(i,j\),每次咱们将\(j\)后移一位以表示再加入一个数,此时若\(y_i\le y_j\)则不断后移\(i\),而且将\(z_i\)加入权值树状数组。
而后如今对于右边的每个数:
那么只要找\(z_i\)小于它的数个数便可,这个咱们直接在树状数组上找便可。
复杂度是比较迷的\(O(n\log n)\),不过因为CQD的常数很小因此能够轻松跑过缅怀各位写树套树的dalao
下面上CODE
#include<cstdio> #include<cctype> #include<algorithm> using namespace std; const int N=100005; struct data { int x,y,z,num,sum; bool operator ==(const data &s) const { return x==s.x&&y==s.y&&z==s.z; } }a[N],q[N]; int n,cnt,m,bit[N<<1],ans[N],tot; inline char tc(void) { static char fl[100000],*A=fl,*B=fl; return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++; } inline void read(int &x) { x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1; while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag; } inline void write(int x) { if (x>9) write(x/10); putchar(x%10+'0'); } inline bool cmpx(data a,data b) { if (a.x==b.x&&a.y==b.y) return a.z<b.z; if (a.x==b.x) return a.y<b.y; return a.x<b.x; } inline bool cmpy(data a,data b) { if (a.y==b.y) return a.z<b.z; return a.y<b.y; } inline int lowbit(int x) { return x&-x; } inline void add(int x,int y) { for (;x<=m;x+=lowbit(x)) bit[x]+=y; } inline int get(int x) { int res=0; for (;x;x-=lowbit(x)) res+=bit[x]; return res; } inline void CDQ(int l,int r) { if (l==r) return; int mid=l+r>>1,id=l; CDQ(l,mid); CDQ(mid+1,r); sort(q+l,q+mid+1,cmpy); sort(q+mid+1,q+r+1,cmpy); for (register int i=mid+1;i<=r;++i) { while (id<=mid&&q[id].y<=q[i].y) add(q[id].z,q[id].num),++id; q[i].sum+=get(q[i].z); } for (register int i=l;i<id;++i) add(q[i].z,-q[i].num); } int main() { //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout); register int i; read(n); read(m); for (i=1;i<=n;++i) read(a[i].x),read(a[i].y),read(a[i].z); for (sort(a+1,a+n+1,cmpx),a[n+1]=(data){-1,-1,-1},i=cnt=1;i<=n;++i) if (a[i]==a[i+1]) ++cnt; else q[++tot]=a[i],q[tot].num=cnt,cnt=1; for (CDQ(1,tot),i=1;i<=tot;++i) ans[q[i].sum+q[i].num-1]+=q[i].num; for (i=0;i<n;++i) write(ans[i]),putchar('\n'); return 0; }
其实我也不会,不过对于通常的高维偏序,咱们能够CDQ套CDQ,反正通常k维偏序用CDQ的复杂度就是\(O(n\log^{k-1} n)\)
所以维数太大时仍是使用K-d tree吧