当前位置: 首页 > news >正文

三维偏序 -- cdq 套 cdq

似乎题解区并没有 cdq 套 cdq 的作法,刚好今天讲了,就来写一发。

题意与记号

题目讲的很清楚了。此方法并没有树状数组好想也没有其高效,但能很方便扩展。下文记原序列为 ddd,将每个点拆分成点与询问,内部增加一个名为 ididid 的数据,若其为 −1-11,则是点,否则是询问。

使用类 c++ 数组的书写方式,否则角标太复杂实在不好看。下文先讲二维再讲三维。

二维偏序

先对 xxx 维排序,再分治 yyy 维,每一次分治中点为 midmidmid 的区间 [l,r)[l,r)[l,r),我们都能保证,也必须保证 d[a].x≤d[b].xd[a].x\le d[b].xd[a].xd[b].xa∈[l,mid)a\in[l,mid)a[l,mid)b∈[mid,r)b\in[mid,r)b[mid,r)
L={d[l],d[l+1],d[l+2],… } ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣ ⁣↑R={d[mid],d[mid+1],… } ⁣ ⁣↑ L=\{d[l],d[l+1],d[l+2],\dots \} \\ \!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\!\! \uparrow \\ R=\{d[mid],d[mid+1],\dots \} \\ \!\! \uparrow L={d[l],d[l+1],d[l+2],}R={d[mid],d[mid+1],}
我们对两边同时向后处理(并非同步),我们正在处理 d[a]d[a]d[a]d[b]d[b]d[b],其中 d[a].id=−1d[a].id=-1d[a].id=1d[b].id≠−1d[b].id\ne-1d[b].id=1(其它情况不用考虑)。

如果 d[a].y≤d[b].yd[a].y\le d[b].yd[a].yd[b].y,由于分治,LLLRRR 内以 yyy 为关键字肯定是有序的,所以 d[a′].y≤d[b′].yd[a'].y\le d[b'].yd[a].yd[b].ya′∈[l,a]a'\in[l,a]a[l,a]b′∈[b,r]b'\in[b,r]b[b,r]

所以此时我们便可以记录 d[a]d[a]d[a] 的贡献(这里是 111),并将 d[a]d[a]d[a] 扔到归并排序的辅助数组里, a←a+1a \leftarrow a+1aa+1

同理,如果 d[a].y>d[b].yd[a].y > d[b].yd[a].y>d[b].y,我们直接由之前的贡献总和便可以计算答案,并将 d[b]d[b]d[b] 扔到归并排序的辅助数组里,b←b+1b\leftarrow b+1bb+1

三维偏序

进入正题,仿照上面的方法,在第一次 cdq 内部,每一层再执行另一种 cdq(cdq2),如果我们能保证 LLLRRR 内部同时关于 xxxyyy 有序就好了,可是我们在分治的过程中必然会将 xxx 打乱,如果有一种方法,能告诉我们 xxx 曾经在哪边,便能够解决这个问题。

也就是说:我们只想要曾经(xxx 维)在左边点的对右边询问的做贡献。

为此,我们扩展 ddd,加入一个名为 tagtagtag 的数据表示在当层是在左边还是右边,由于此时 yyy 早已有序,仿照二维进行 cdq2。
解释见代码吧,超详细的!

namespace PB {node d[N];                                             // 存储当前处理的节点数组,包含点和查询void solve(int l, int r) {                             // 处理区间 [l, r) 的 z 维偏序if (l == r - 1) return;                            int mid = (l + r) >> 1;                            solve(l, mid), solve(mid, r);                      int a = l, b = mid, c = l, sum = 0;                // sum 记录左边点的数量while (a < mid || b < r) {                         // 归并排序,按 z 坐标合并if (b == r || (a < mid && d[a].z <= d[b].z)) { // 左边还有元素且 z 更小(或右边已空)if (d[a].id == -1 && d[a].tag == LEFT)     // 如果是左边区间的一个点++sum;                                 tp[c++] = d[a++];                          } else {                                       // 右边 z 更小(或左边已空)if (d[b].id != -1 && d[b].tag == RIGHT)    // 如果是右边区间的查询anst[d[b].id] += sum;                  // 累加当前左边点的数量到查询结果tp[c++] = d[b++];                          }}copy(tp + l, tp + r, d + l); // 将临时数组复制回原数组,完成归并}
} // namespace PBnamespace PA {void solve(int l, int r) {                             // 处理区间 [l, r) 的 y 维偏序if (l == r - 1) return;                            int mid = (l + r) >> 1;                            solve(l, mid), solve(mid, r);                      // 递归处理左半区间 [l, mid) 和右半区间 [mid, r)int a = l, b = mid, c = l;                         // a, b 分别指向左右区间,c 指向临时数组while (a < mid || b < r) {                         // 归并排序,按 y 坐标合并if (b == r || (a < mid && d[a].y <= d[b].y)) { // 左边还有元素且 y 更小(或右边已空)d[a].tag = LEFT, tp[c++] = d[a++];         // 标记为左区间} else {                                       // 右边 y 更小(或左边已空)d[b].tag = RIGHT, tp[c++] = d[b++];        // 标记为右区间}}copy(tp + l, tp + r, d + l);     // 将临时数组复制回原数组,完成 y 维归并copy(tp + l, tp + r, PB::d + l); // 将排序后的数组复制到 PB 命名空间的 d 数组PB::solve(l, r);                 // 调用 PB 处理 z 维偏序}
} // namespace PA

时间复杂度

PB 中一次处理长度为 nnn 的区间 O(n)=nlog⁡nO(n)=n\log nO(n)=nlogn

PA(总):O(∑i=0log⁡nT(n2i)×2i)=O(nlog⁡2n)O(\sum_{i=0}^{\log n}T(\frac{n}{2^i})\times 2^i) = O(n\log^2n)O(i=0lognT(2in)×2i)=O(nlog2n)

http://www.lryc.cn/news/612103.html

相关文章:

  • 一文读懂:什么是CLIP
  • 目录遍历漏洞学习
  • 560. 和为 K 的子数组 - 前缀和思想
  • kubeadm-k8s 中的 etcd 备份与恢复
  • Nginx 跨域(CORS)配置详细介绍
  • 【教程】C++编译官方CEF3
  • [Oracle] NVL()函数
  • Python:文件管理
  • 玳瑁的嵌入式日记D13-0806(C语言)
  • 【运维进阶】DHCP服务配置和DNS域名解析
  • TypeScript ActionScript
  • 浅谈RNN被Transformer 取代的必然性
  • Kotlin Native调用C curl
  • Uniapp生物识别(SOTER)
  • 【第5话:相机模型1】针孔相机、鱼眼相机模型的介绍及其在自动驾驶中的作用及使用方法
  • 第二十六天(数据结构:树(补充版程序请看下一篇))
  • 数字图像处理(冈萨雷斯)第三版:第四章——空间滤波与频域滤波(平滑与锐化)——主要内容和重点
  • 【PHP 抽象类完全指南(含 PHP 8.4 新特性)】
  • 02.【数据结构-C语言】顺序表(线性表概念、顺序表实现:增删查、前向声明、顺序表实现通讯录项目:增删改查、通讯录数据导入及保存到本地文件)
  • Linux操作系统启动项相关研究与总结
  • Redis面试精讲 Day 12:Redis Sentinel哨兵机制详解
  • 深度学习(pytorch版)前言:环境安装和书籍框架介绍
  • 单变量单步时序预测:CNN-GRU卷积神经网络结合门控循环单元
  • Linux系统编程——环境变量、命令行参数
  • mysql8.0主从节点克隆
  • Numpy科学计算与数据分析:Numpy入门之多平台安装与基础环境配置
  • 用NAS如何远程访问:详细教程与实用技巧
  • 强强联合:OpenAI正式登陆AWS!
  • 【motion】标签体系设计与检索 1:HumanML3D 和 KIT Motion-Language(KITML)
  • 《Vue 3与Element Plus构建多语后台的深层架构》