解法:CDQ分治
求解三维偏序问题的方法很多,我用的是 CDQ 分治,比较好写,但时间复杂度会劣一些。
CDQ 分治是离线算法,可以解决一些与点对有关的问题,也可以将动态问题转为静态。在此题中,我们有三个维度 a , b , c a,b,c a,b,c,运用 CDQ 解决的流程如下:
①:先按 a a a 从小到大排序,这样满足任意 j > i j>i j>i,有 a j > = a i a_j>=a_i aj>=ai。
②:进入 CDQ 分治,设此时处理区间为 [ l , r ] [l,r] [l,r],令中点为 m i d mid mid,先分别递归处理左右两个子区间,然后再考虑跨区间的贡献。类似分治思想。
③:我们将 [ l , m i d ] [l,mid] [l,mid] 和 [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 两段区间内按 b b b 排序,接下来设 i i i 和 j j j 两个指针,找到所有 i < = m i d i<=mid i<=mid 且 b i < = b j b_i<=b_j bi<=bj,这个时候 a a a 和 b b b 两维都满足了,我们可以开一个值域树状数组,用以维护 c c c 的数量。最终就求出答案了。
时间复杂度: O ( n l o g 2 n ) O(n_{}log^2n) O(nlog2n)
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
int n,m,k,tr[N],ans[N];
struct node{
int a,b,c,res,cnt;
bool operator !=(node x){
if(a!=x.a) return true;
if(b!=x.b) return true;
if(c!=x.c) return true;
return false;
}
}q[N],e[N];
bool cmpa(node a,node b){
if(a.a!=b.a) return a.a<b.a;
if(a.b!=b.b) return a.b<b.b;
return a.c<b.c;
}
bool cmpb(node a,node b){
if(a.b!=b.b) return a.b<b.b;
return a.c<b.c;
}
int lowbit(int x){return x&-x;}
void add(int x,int v){for(int i=x;i<=2e5;i+=lowbit(i))tr[i]+=v;}
int query(int x){int res=0;for(int i=x;i;i-=lowbit(i))res+=tr[i];return res;}
void CDQ(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
CDQ(l,mid),CDQ(mid+1,r);
sort(e+l,e+mid+1,cmpb),sort(e+mid+1,e+r+1,cmpb);
int i=l,j=mid+1;
while(j<=r){
while(i<=mid&&e[i].b<=e[j].b) add(e[i].c,e[i].cnt),i++;
e[j].res+=query(e[j].c),j++;
}
for(int k=l;k<i;k++) add(e[k].c,-e[k].cnt);//别忘了清空,防止对之后计算产生影响
}
int main(){
n=read(),k=read();
for(int i=1;i<=n;i++) q[i].a=read(),q[i].b=read(),q[i].c=read();
sort(q+1,q+1+n,cmpa);
int t=0;
for(int i=1;i<=n;i++){//a,b,c都相等的点视为一个,防止干扰。
t++;
if(q[i]!=q[i+1]){
e[++m].a=q[i].a,e[m].b=q[i].b,e[m].c=q[i].c;
e[m].cnt=t,t=0;
}
}
CDQ(1,m);
for(int i=1;i<=m;i++) ans[e[i].res+e[i].cnt-1]+=e[i].cnt;
for(int i=0;i<n;i++) cout<<ans[i]<<endl;
return 0;
}