P3810 【模板】三维偏序(陌上花开)
CDQ分治
先三关键字排序
然后把第二关键字归并排序
在合并子区间时用 第三关键字的权值树状树组 算出子区间的答案
为什么可以这样搞呢
首先第一维已经有序
所以只要考虑左边对右边的影响
把第二维归并时 左子区间的第一二关键字 全部小于或等于 右子区间的第一二关键字
所以也只要考虑左边对右边的影响
就用 第三关键字权值树状树组 搞一搞 二维偏序 就好了
二维偏序应该懂吧,实现和细节还是看代码吧
还要注意有重复的数
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> using namespace std; inline int read() { int x=0; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x; } const int N=4e5+7; int n,m; int a[N],b[N],c[N],p[N],cnt[N],ans[N],v[N]; //a,b,c为三个关键字,p存排序去重后的数据,这里没用结构体,可以省空间 //v是每个数重复的数量 inline bool cmp1(const int &x,const int &y){ return a[x]<a[y]|| (a[x]==a[y]&& (b[x]<b[y]|| (b[x]==b[y]&&c[x]<c[y]) ) ); } //不用结构体排序的骚操作 int t[N];//数状数组 inline void add(int x,int v) { while(x<=m) { t[x]+=v; x+=x&-x; } } inline int query(int x) { int res=0; while(x) { res+=t[x]; x-=x&-x; } return res; } int tmp[N];//归并时的临时数组 void cdq(int l,int r) { if(l==r) return; int mid=(l+r)>>1; cdq(l,mid); cdq(mid+1,r);//先归并处理子区间 int i=l,j=mid+1,tot=0; while(i<=mid&&j<=r) { int x=p[i],y=p[j]; if(b[x]<=b[y]) add(c[x],v[x]),tmp[++tot]=x,i++; else cnt[y]+=query(c[y]),tmp[++tot]=y,j++; }//核心,树状数组维护二维偏序 while(j<=r) cnt[p[j]]+=query(c[p[j]]),tmp[++tot]=p[j],j++; while(i<=mid) tmp[++tot]=p[i],i++; //可能还有一些没处理完 int mx=b[p[r]]; for(i=l; i<=mid&&b[p[i]]<=mx ;i++) add(c[p[i]],-v[p[i]]);//用完要这样清空 for(i=l;i<=r;i++) p[i]=tmp[i-l+1];//归并排序 } int main() { n=read(); m=read(); for(int i=1;i<=n;i++) p[i]=i,a[i]=read(),b[i]=read(),c[i]=read(); sort(p+1,p+n+1,cmp1); int tot=1; for(int i=2;i<=n;i++) { int x=p[i],y=p[tot]; v[y]++; if(a[x]^a[y]||b[x]^b[y]||c[x]^c[y]) p[++tot]=x; } v[p[tot]]++; //去重并计算重复的数量 cdq(1,tot); for(int i=1;i<=tot;i++) ans[cnt[p[i]]+v[p[i]]-1]+=v[p[i]];//注意有重复的数字 for(int i=0;i<n;i++) printf("%d\n",ans[i]); return 0; }