陌上花开(三维偏序 CDQ分治)
题目描述
有 n 个元素,第 i 个元素有 ai、bi、ci 三个属性,设 f(i) 表示满足 aj≤ai 且 bj≤bi 且cj≤ci 的 j 的数量。
对于 d∈[0,n),求 f(i)=d 的数量
思路
对于二维偏序,我们可以对第一维排序,然后用按顺序树状数组维护第二维即可。考虑另一种方式,
在处理第二维时,将这些数分成两半,左边的数打标记,接着再按第二维排序,考虑左边对右边的贡
献,就是没打标记的数前打了标记的数,这个直接从头for一遍即可,遇到标记就cnt++,没标记就记
录答案,对于自己那一边的数自己的贡献,递归处理。
对于三维偏序,我们可以用类似的方法,只是在遇到带标记的数时加入树状数组,没标记就查询答案。
代码
与上面思路略有不同,在回归时分别对左右两边按第二维排序,左右两边第一维的相对大小仍存在,
用双指针在两边移动,将左边的数加入树状数组,再统计贡献。
细节
去重,对于重复的三元组,并不是每个三元组都能统计到对方,所以要去重并记录个数。
在CDQ排序时两个端点。
递归在每层最后要清零,但不能memset会超时,需要一个一个加负的。
统计答案时,a[i].w-1是统计与自己重复的数
去重时要for到n,不然最后一个数不能放进a数组
按x排序时不能单纯按x排,因为这样就不能去重
#include<bits/stdc++.h> using namespace std; const int maxn=100005,maxk=200005; int n,k,_n; int tr[maxk<<1],cnt[maxn]; struct point { int x,y,z,w,ans; }a[maxn],b[maxn]; template<class T>inline void read(T &x){ x=0;char ch=getchar(); while(!isdigit(ch)) ch=getchar(); while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} } bool cmp1(point a,point b){ if(a.x==b.x){ if(a.y==b.y) return a.z<b.z; return a.y<b.y; } return a.x<b.x; } bool cmp2(point a,point b){ if(a.y==b.y) return a.z<b.z; return a.y<b.y; } void add(int pos,int v){ for(int i=pos;i<=k;i+=i&-i) tr[i]+=v; } int sum(int pos){ int ret=0; for(int i=pos;i;i-=i&-i) ret+=tr[i]; return ret; } void cdq(int l,int r){ if(l==r) return ; int mid=(l+r)>>1; cdq(l,mid); cdq(mid+1,r); sort(a+l,a+mid+1,cmp2); sort(a+mid+1,a+r+1,cmp2); int i=mid+1,j=l; for(;i<=r;i++){ while(a[j].y<=a[i].y&&j<=mid){ add(a[j].z,a[j].w); j++; } a[i].ans+=sum(a[i].z); } for(i=l;i<j;i++) add(a[i].z,-a[i].w); } int main(){ read(n);read(k); for(int i=1;i<=n;i++) read(b[i].x),read(b[i].y),read(b[i].z); sort(b+1,b+n+1,cmp1); int c=0; for(int i=1;i<=n;i++){ c++; if(b[i].x!=b[i+1].x||b[i].y!=b[i+1].y||b[i].z!=b[i+1].z) a[++_n]=b[i],a[_n].w=c,c=0; } cdq(1,_n); for(int i=1;i<=_n;i++) cnt[a[i].ans+a[i].w-1]+=a[i].w; for(int i=0;i<n;i++) printf("%d\n",cnt[i]); }