经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
题解-P3810
来源:cnblogs  作者:One_Zzz  时间:2021/6/15 9:19:32  对本文有异议

P3810 【模板】三维偏序(陌上花开)

更好的阅读体验1

更好的阅读体验2


前置算法

  • 树状数组求逆序对
  • 归并排序求逆序对

解题之前,让我们来看一看弱化版本 \(\to\) 二维偏序


题意

给定两个长度为数组 \(a_1,a_2,\dots,a_n\)\(b_1,b_2,\dots,b_n\) 求对于每一个 \(i\)\(a_j\le a_i\)\(b_j\le b_i\)\(j\) 有多少个。

解法1

考虑用结构体把数组存起来,对 \(a\) 进行排序,再用一个值域树状数组维护 \(b\) 值即可。

还没完。由于可能出现 \(a_i=a_j\)\(b_i=b_j\) 的情况,所以需要去重。

提到去重,就要在结构体里面多加一个 \(x\)\(x_i\) 表示与 \(a_j=a_i,b_j=b_i\)\(j\) 的个数,\(x_i\) 初始为 \(1\)

去重毒瘤的要死

解法2

还是用结构体存,对 \(a\) 进行排序+去重,后面考虑用归并排序来求

回想一下归并排序求逆序对,我们求的是 \(a_i\) 作为逆序对 \((j,i)\)\(j\) 的总个数。

在这里,\(a\) 已经按大小排好了,所以我们只考虑对 \(b\) 值求逆序对就行了。

深刻注意解法2


三维偏序

第一步

和二维偏序一样,先按 \(a\) 值排好序,去重

第二步

为了简化题目,先考虑简易版本不存在 \(a_i=a_j\)\(b_i=b_j\)\(c_i=c_j\) 的情况,标准版本放在第三步。

进入到今天的正题: CDQ 讲解部分。

CDQ 分治,顾名思义,是一种分治。而分治,就需要把 \([l,r]\) 分为 \([l,mid]\)\([mid+1,r]\) 。而对于我们要寻找的一个符合 \(a_j\le a_i,b_j\le b_i,c_j\le c_i\) 的点对 \((i,j)\) 必须符合以下三种情况之一:

  1. \(1\le i,j\le mid\gets\) 这在递归处理左半边时已经处理了
  2. \(mid\lt i,j\le n\gets\) 这在递归处理右半边时已经处理了
  3. \(1\le i,j\le n\gets\) 这是我们在递归之后需要处理的点对

按分治三部曲走,接下来就是合并左右区间并统计答案了了,这里按归并排序求逆序对的思路来。

由于 \(a,b\) 值都被我们处理好了,接下来就是毒瘤的 \(c\) 值,用树状数组维护。

在合并中,我们分两种情况:

  1. 此时的最小值在左,那么我们让树状数组的左边那个数的 \(c\) 值位置加一
  2. 此时的最小值在右,那么我们让答案加上树状数组从 \(1\) 到右边数的 \(c\) 值位置的和

如果搞不懂为什么左是加,右是查,建议重新看一看归并排序求逆序对。

注意:每一次使用完后树状数组要清空。如果单纯 memset,会超时(因为是 \(O(n)\) ),清空应该对于每一个被存放在树状数组里的 \(c_i\) ,其在树状数组里面的值 \(-1\)

  1. int tmp[maxn]; // 临时存放合并好的值的数组
  2. void CDQ(int l,int r){
  3. if(l == r) return; 
  4. int mid = l + r >> 1,p = l,q = mid + 1,len = 0;
  5. CDQ(l,mid),CDQ(mid + 1,r); // 递归处理
  6. while(p <= mid && q <= r){ // 合并子区间
  7. if(a[p].b <= a[q].b) bit.update(a[p].c,1),tmp[++len] = a[p++]; // 选左边,此时更新树状数组
  8. else a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++]; // 选右边,此时答案要加上值域树状数组的查询
  9. }
  10. while(p <= mid) bit.update(a[p].c,1),tmp[++len] = a[p++]; // 归并左边剩下部分
  11. while(q <= r) a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++]; // 归并右边剩下部分
  12. for(int i = l;i <= mid;++i) bit.update(a[i].c,-1); // 清空
  13. for(int i = 1;i <= len;++i) a[l + i - 1] = tmp[i]; // 把临时数组里的值拷贝到原数组
  14. }

第三步

由于第二步只能处理不存在相同的情况,接下来讲解如果存在 \(a_i=a_j,b_i=b_j,c_i=c_j\)\((i,j)\) 该怎么处理。

注意刚才我们树状数组是这样更新的:bit.update(a[p].c,1)

这里的 1 实际上就是 \(a_s=a_p,b_s=b_p,c_s=c_p\)\(s\) 的个数,记为 \(x\)\(x_i\) 我们在去重时求出。

因此代码如下:

  1. int tmp[maxn]; 
  2. void CDQ(int l,int r){
  3. if(l == r) return;
  4. int mid = l + r >> 1,p = l,q = mid + 1,len = 0;
  5. CDQ(l,mid),CDQ(mid + 1,r);
  6. while(p <= mid && q <= r){
  7. if(a[p].b <= a[q].b) bit.update(a[p].c,a[p].x),tmp[++len] = a[p++];
  8. else a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++];
  9. }
  10. while(p <= mid) bit.update(a[p].c,a[p].x),tmp[++len] = a[p++];
  11. while(q <= r) a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++];
  12. for(int i = l;i <= mid;++i) bit.update(a[i].c,-a[i].x);
  13. for(int i = 1;i <= len;++i) a[l + i - 1] = tmp[i];
  14. }

第四步

这时 CDQ 分治已经完成了,我们现在需要统计答案

按照刚才的代码,a[i].ans 表示 \(a_j\le a_i,b_j\le b_i,c_j\le c_i\) 但不包括 \(a_j=a_i,b_j=b_i,c_j=c_i\)\(j\) 的个数,而 a[i].x 正好表示了 \(a_j=a_i,b_j=b_i,c_j=c_i\)\(j\) 的个数。于是 a[i].ans + a[i].x 就是去重后第 \(i\) 个点的答案。

  1. for(int i = 1;i <= cnt;++i) res[a[i].ans + a[i].x - 1] += a[i].x; // 注意是 + a[i].x,因为还有与 i 值相同所有 j,其总个数是 a[i].x
  2. for(int i = 0;i < n;++i) printf("%d\n",res[i]);

\(\color{#52C41A}\texttt{AC CODE}\)

  1. #include<stdio.h>
  2. #include<algorithm>
  3. const int maxn = 114514;
  4. int n,k;
  5. struct BIT{
  6. int t[maxn << 1];
  7. int lowbit(int i){return i & -i;}
  8. void update(int i,int x){for(;i <= k;i += lowbit(i)) t[i] += x;}
  9. int query(int i){int ans = 0;for(;i;i -= lowbit(i)) ans += t[i];return ans;}
  10. } bit; // 树状数组
  11. struct number{
  12. int a,b,c,ans,x;
  13. bool operator<(const number& y) const{return a != y.a ? a < y.a : b != y.b ? b < y.b : c < y.c;}
  14. } a[maxn],tmp[maxn]; // 数的结构体
  15. int res[maxn];
  16. void CDQ(int l,int r){
  17. if(l == r) return;
  18. int mid = l + r >> 1,p = l,q = mid + 1,len = 0;
  19. CDQ(l,mid),CDQ(mid + 1,r);
  20. while(p <= mid && q <= r){
  21. if(a[p].b <= a[q].b) bit.update(a[p].c,a[p].x),tmp[++len] = a[p++];
  22. else a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++];
  23. }
  24. while(p <= mid) bit.update(a[p].c,a[p].x),tmp[++len] = a[p++];
  25. while(q <= r) a[q].ans += bit.query(a[q].c),tmp[++len] = a[q++];
  26. for(int i = l;i <= mid;++i) bit.update(a[i].c,-a[i].x);
  27. for(int i = 1;i <= len;++i) a[l + i - 1] = tmp[i];
  28. }
  29. int main(){
  30. scanf("%d%d",&n,&k);
  31. for(int i = 1;i <= n;++i) scanf("%d%d%d",&a[i].a,&a[i].b,&a[i].c),a[i].x = 1,a[i].ans = 0;
  32. std::sort(a + 1,a + n + 1); // 排序
  33. int cnt = 1;
  34. for(int i = 2;i <= n;++i)
  35. if(a[i].a == a[cnt].a && a[i].b == a[cnt].b && a[i].c == a[cnt].c) ++a[cnt].x; // 如果遇到与 i 一样的,x 值就要自加一
  36. else a[++cnt] = a[i];
  37. CDQ(1,cnt); // 注意不是 CDQ(1,n)
  38. for(int i = 1;i <= cnt;++i) res[a[i].ans + a[i].x - 1] += a[i].x;
  39. for(int i = 0;i < n;++i) printf("%d\n",res[i]);
  40. return 0;
  41. }

原文链接:http://www.cnblogs.com/zeno6174/p/solution-p3810.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号