cdq分治
本来我是会cdq分治的,但是我现在已经有些忘却了
所以初略写一下cdq分治,想要更详细的可以出门右转在下方评论
cdq分治的本质是用到了归并排序的,
所以不知道归并排序怎么打的人可以先去学归并排序,
先上三维偏序的cdq分治代码.. 模板题传送门
#include<bits/stdc++.h> #define lowbits(i) i&(-i) using namespace std; const int N=1e5+1e4; int n,k,m,b[N*4],c[N]; struct PP{ int a,b,c,w,ans; }a[N],tmp[N]; int rd(){ int s=0,ff=1; char w=getchar(); while(w<'0'||w>'9'){ if(w=='-') ff=-1; w=getchar(); } while(w>='0'&&w<='9'){ s=s*10+(w-'0'); w=getchar(); } return s*ff; } bool cmp(PP aa,PP bb){ return aa.a==bb.a?(aa.b==bb.b?aa.c<bb.c:aa.b<bb.b):aa.a<bb.a; } void Add(int x,int y){ for(int i=x;i<=k;i+=lowbits(i)){ b[i]+=y; } } void Del(int x){ for(int i=x;i<=k;i+=lowbits(i)){ b[i]=0; } } int Query(int x){ int sum=0; for(int i=x;i>0;i-=lowbits(i)){ sum+=b[i]; } return sum; } bool operator<(PP aa,PP bb){ return aa.b==bb.b?aa.c<=bb.c:aa.b<bb.b; } void Cdq(int l,int r){ if(l==r){ return ; } int mid=(l+r)>>1; Cdq(l,mid); Cdq(mid+1,r); int t=l,L=l,M=mid+1; while(L<=mid&&M<=r){ if(a[L]<a[M]){ Add(a[L].c,a[L].w); tmp[t++]=a[L++]; } else{ a[M].ans+=Query(a[M].c); tmp[t++]=a[M++]; } } while(L<=mid){ Add(a[L].c,a[L].w); tmp[t++]=a[L++]; } while(M<=r){ a[M].ans+=Query(a[M].c); tmp[t++]=a[M++]; } for(int i=l;i<=r;i++){ a[i]=tmp[i]; Del(a[i].c); } } int main(){ int minn=1e9; m=rd(); k=rd(); for(int i=1;i<=m;i++){ a[i]=(PP){rd(),rd(),rd(),1,0}; minn=min(minn,a[i].c); } minn--; k-=minn; for(int i=1;i<=m;i++){ a[i].c-=minn; } sort(a+1,a+1+m,cmp); for(int i=1;i<=m;i++){ if(a[i].a==a[i-1].a&&a[i].b==a[i-1].b&&a[i].c==a[i-1].c){ a[i].w=a[i-1].w+1; a[i-1]=(PP){1e9,1e9,1e9,0,0}; } } sort(a+1,a+1+m,cmp); n=m; while(!a[n].w) {n--;} Cdq(1,n); for(int i=1;i<=n;i++){ c[a[i].ans+a[i].w-1]+=a[i].w; } for(int i=0;i<m;i++){ printf("%d\n",c[i]); } return 0; }
有的人可能会说“别人都是先讲原理再上代码,你怎么直接就上代码了”,因为
我乐意(不是
话回正题,先解释时间复杂度,
因为cdq分治的过程是在归并排序中进行的,
所以cdq分治的时间复杂度为O(nlog n),
但是这题(三维偏序)需要用到sort+树状数组+cdq分治,
所以时间复杂度为O(nlog n log k)(k为最大元素值)。
这题也可以用sort+cdq分治+cdq分治,
时间复杂度为O(nlog2n)。
下面是原理
用二维偏序解释吧,这样好解释点,
(第一维是a[],第二维是b[],
求f[i]为a[i]>=a[j]&&b[i]>=b[j]的j的个数)
当然二维偏序可以用(sort+树状数组)过,
但是我们就是要用(sort+cdq分治)过,
首先用sort解决一维,使其单调排序,
然后第二维的话如图所示(图中视为第一维已经排好序了):
先是归并排序向下递归
再是归并排序向上合并(左边的指针为i,右边的指针为j)
就是在合并的时候要统计左边一块对右边一块的答案贡献,
因为左边的a是小于等于右边的a的,所以不管,
当指针i所在的数小于等于指针j所在的数,
说明在j之后的数的答案贡献都可以加上1,
用一个变量累加存过去就好了,
当然你得存下一个数一开始在数列中的位置,
代码如下:
void Cdq(int l,int r){ if(l==r){ return ; } int mid=(l+r)>>1; Cdq(l,mid); Cdq(mid+1,r); int t=l,i=l,j=mid+1,ss=0; while(i<=mid&&j<=r){ if(b[i]<=b[j]){ ss++,tmp[t++]=b[i++]; } else{ f[w[b[j]]]+=ss,tmp[t++]=b[j++];//w[b[j]]表示b[j]这个数在数列中的位置; } } while(i<=mid){ ss++,tmp[t++]=b[i++]; } while(j<=r){ f[w[b[j]]]+=ss,tmp[t++]=b[j++]; } for(int i=l;i<=r;i++){ b[i]=tmp[i]; } }
所以cdq分治主要的思想就是:
分治->合并->算左边对右边的贡献
最后再强调下单用cdq分治时间复杂度是O(nlog n)的,
三维偏序中再加上树状数组才是O(nlog n log k)的。
就写到这了。
不懂的话发评论问我,懂了的话点个赞。