洛谷P3810【模板】三维偏序(陌上花开)
题目背景
这是一道模板题
可以使用bitset,CDQ分治,K-DTree等方式解决。
题目描述
有 n 个元素,第 i 个元素有 ai 、 bi 、 ci 三个属性,设 f(i) 表示满足 aj≤ai 且 bj≤bi 且 cj≤ci 的 j 的数量。
对于 d∈[0,n) ,求 f(i)=d 的数量
输入格式:
第一行两个整数 n 、 k ,分别表示元素数量和最大属性值。
之后 n 行,每行三个整数 ai 、 bi 、 ci ,分别表示三个属性值。
输出格式:
输出 n 行,第 d+1 行表示 f(i)=d 的 i 的数量。
输入输出样例
说明
1≤n≤100000,1≤k≤200000
题解:
今儿个新学了算法:cdq分治
此题为cdq分治三维偏序的模板题。
我们先按照第一维从小到大排序,相等的话比下一维,以此类推。
排完序后有个注意点,序列中的元素可能存在重复,于是要去重,代码见下:
1 for (int i=1; i<=n; i++) 2 if (a[i].x==a[i-1].x && 3 a[i].y==a[i-1].y && 4 a[i].z==a[i-1].z) a[m].t++; 5 else a[++m]=a[i];
其中 a[i].t 存的是与之相等的元素的个数(包括自己)。
接下来就开始cdq了:
消除了第一维的影响,我们只需按第二维从小到大归并,第三维用树状数组维护就行了。
还有一个要点(千万别忘了): 树状数组的数组记得清空,也不需要全部memset,只需把修改过的节点清空就可以了,效率大大提高,如下:
for (int i=l; i<=mid; i++) change(a[i].z,-a[i].t);
就讲到这里吧,代码如下:
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=100005,K=200005,inf=0x3f3f3f3f; 4 struct node{ 5 int x,y,z,t,s; 6 bool operator < (const node &aa) const { 7 return x==aa.x?(y==aa.y?z<aa.z:y<aa.y):x<aa.x; 8 } 9 }a[N]; 10 int n,m,k,ans[N],cnt[K]; 11 inline int read() 12 { 13 int x=0,f=1; 14 char ch=getchar(); 15 while (!isdigit(ch)) 16 f=(ch=='-1')?-1:1,ch=getchar(); 17 while (isdigit(ch)) 18 x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); 19 return x*f; 20 } 21 node tmp[N]; 22 inline int lowbit(int x) 23 { 24 return x&(-x); 25 } 26 void change(int x,int w) 27 { 28 for (int i=x; i<=k; i+=lowbit(i)) 29 cnt[i]+=w; 30 } 31 inline int get(int x) 32 { 33 int sum=0; 34 for (int i=x; i; i-=lowbit(i)) 35 sum+=cnt[i]; 36 return sum; 37 } 38 void cdq(int l,int r) 39 { 40 if (l==r) return; 41 int mid=(l+r)>>1; 42 cdq(l,mid),cdq(mid+1,r); 43 int u=l,v=mid+1; 44 for (int i=l; i<=r; i++) 45 if (v>r || (a[u].y<=a[v].y && u<=mid)) 46 change(a[u].z,a[u].t),tmp[i]=a[u++]; 47 else a[v].s+=get(a[v].z),tmp[i]=a[v++]; 48 for (int i=l; i<=mid; i++) change(a[i].z,-a[i].t); 49 for (int i=l; i<=r; i++) a[i]=tmp[i]; 50 } 51 int main() 52 { 53 n=read(),k=read(),m=0,a[0].x=a[0].y=a[0].z=-inf; 54 for (int i=1; i<=n; i++) 55 a[i].x=read(),a[i].y=read(),a[i].z=read(),a[i].t=1; 56 sort(a+1,a+1+n); 57 for (int i=1; i<=n; i++) 58 if (a[i].x==a[i-1].x && 59 a[i].y==a[i-1].y && 60 a[i].z==a[i-1].z) a[m].t++; 61 else a[++m]=a[i]; 62 cdq(1,m); 63 for (int i=1; i<=m; i++) ans[a[i].t+a[i].s-1]+=a[i].t; 64 for (int i=0; i<n; i++) printf("%d ",ans[i]); 65 return 0; 66 }
这里再补充点洛谷P3157动态逆序对的解法:
首先动态操作比较麻烦,想法办转成静态的:
我们让被删除的数排在未被删除的数的后面,删除早的数排在删除晚的数的后面,这样一来左边的右边的就不会对左边的产生影响,而左边的仍旧可以对右边产生影响,于是就OK了。
还有一个要注意的是:按照这样排序后,以下两种情况都是符合逆序对的:
xi<xj && yi>yj
xi>xj && yi<yj
要用两个树状数组分别维护。
代码就不贴了,可参见其他dalao。
加油加油加油!!! fighting fighting fighting !!!