题目
以三维偏序为例来讲一下CDQ分治。
CDQ的本质就是把一个序列分成两段,计算左边对右边的贡献,然后分治。
不过一般都是先分治到底再从下往上算,这样可以先归并再算。
比如这道题,我们先按第一维排序,然后分治完下一层之后边归并排序边算贡献。
具体大概是这样:
比如我们已经把下面的全部算完了,那么传上来的左边和有边一定满足:
1、左边的第一维都比右边的第一维小。
2、左边和右边在第二维上都是升序的。
那么我们归并排序,遇到左边的就加入数据结构修改贡献,遇到右边的就计算贡献。
同时我们能够保证传上去的是在第二维上是升序的。
在这一题的话,因为我们满足左边的第一维都比右边的小,两边的第二维都是升序,所以我们在归并的过程中只要对每个右边的计算前面的左边的第三维比它小的个数。这个可以通过离散化+去重+BIT维护桶解决。
跟整体二分一样,最后的清零要用撤销而不是memset,否则复杂度bomb。
#include<bits/stdc++.h>
using namespace std;
namespace IO
{
char ibuf[(1<<21)+1],obuf[(1<<21)+1],st[15],*iS,*iT,*oS=obuf,*oT=obuf+(1<<21);
char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
void Flush(){fwrite(obuf,1,oS-obuf,stdout),oS=obuf;}
void Put(char x){*oS++=x;if(oS==oT)Flush();}
int read(){int x=0,c=Get();while(!isdigit(c))c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return x;}
void write(int x){int top=0;if(!x)Put('0');while(x)st[++top]=(x%10)+48,x/=10;while(top)Put(st[top--]);Put('\n');}
}
using namespace IO;
const int N=100007,M=N<<1;
int k,p[N],q[N],a[N],b[N],c[N],t[M],v[N],cnt[N],ans[N];
int cmp(int x,int y){return a[x]<a[y]||(a[x]==a[y]&&(b[x]<b[y]||(b[x]==b[y]&&c[x]<c[y])));}
void modify(int i,int v){for(;i<=k;i+=i&-i)t[i]+=v;}
int query(int i){int sum=0;for(;i;i-=i&-i)sum+=t[i];return sum;}
void cdq(int *p,int n)
{
if(n==1) return;
int m=n>>1,i,j,k,x,y;
for(cdq(p,m),cdq(p+m,n-m),memcpy(q,p,n<<2),k=i=0,j=m;i<m&&j<n;++k)
{
x=q[i],y=q[j];
if(b[x]<=b[y]) modify(c[p[k]=x],v[x]),++i; else cnt[y]+=query(c[p[k]=y]),++j;
}
for(;j<n;++j) cnt[q[j]]+=query(c[q[j]]);
for(memcpy(p+k,q+i,(m-i)<<2),--i;~i;--i) modify(c[q[i]],-v[q[i]]);
}
int main()
{
int n=read(),i,j,x,y;
for(k=read(),i=0;i<n;++i) p[i]=i,a[i]=read(),b[i]=read(),c[i]=read();
for(sort(p,p+n,cmp),i=1,j=0;i<n;++i)
{
x=p[i],y=p[j],++v[y];
if(a[x]^a[y]||b[x]^b[y]||c[x]^c[y]) p[++j]=x;
}
++v[p[j++]],cdq(p,j);
for(i=0;i<j;++i) ans[cnt[p[i]]+v[p[i]]-1]+=v[p[i]];
for(i=0;i<n;++i) write(ans[i]);
return Flush(),0;
}