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

CDQ 分治学习笔记

CDQ 分治学习笔记

概念

  • \(k\) 维偏序:
    • 在一个由 \(k\) 元组构成的集合 \(A\) 当中,一般用 \(\prec\) 表示的一种二元关系。
    • 具有自反性、反对称性、传递性的二元关系。
    • 自反性:\(\forall x \in A,x \prec x\)
    • 反对称性:若 \(x \prec y\)\(y \prec x\),有 \(x = y\)
    • 传递性:若 \(x \prec y\)\(y \prec z\),有 \(x \prec z\)
    • \(\le\) 就是一种偏序。

三维偏序问题

引入

给定一个序列,每个点有 \(a_i\)\(b_i\)\(c_i\) 三个属性,试求:这个序列里有多少对点对 \((i, j)\) 满足 \(a_j \le a_i\)\(b_j \le b_i\)\(c_j \le c_i\)\(i \ne j\)

思路

我们可以先按 \(a\) 排个序,这样就只剩下了 \(b\)\(c\),我们可以通过分治的思想,先处理 \(L \sim mid\),再处理 \(mid + 1 \sim R\),最后处理他们之间的影响。

像这种分治的思想,就叫 CDQ 分治。

回到这道题,前面两种直接往下递归即可,可是最后一种怎么办呢?

我们可以在分治的时候提前把 \([L, mid]\)\([mid + 1, R]\)\(y\) 排好序,用双指针合并。

此时顺手把左半截\(z\) 插入到树状数组中,在右半截的地方记录答案,就做完了。

注意要离散化 \(z\)(因为是严格偏序)。

例题

CF1045G AI robots。

思路

\(r\) 降序排序后,转换一下条件(去绝对值)可得:

\[\left\{ \begin{array}{lr} x_j - r_j \le x_i \le x_j + r_j \\ q_j - k \le q_i \le q_j + k \end{array} \right. \]

直接 CDQ 分治即可。

代码

AI 加注释。

#include <bits/stdc++.h>
#define il inline
#define pii pair<int, int>
#define ppp(QWQ, QAQ) pair<QWQ, QAQ>
#define pb emplace_back
#define pf emplace_front
#define mpa make_pair
#define pqs(QWQ) priority_queue<QWQ, vector<QWQ>, greater<QWQ> >
#define pqb(QWQ) priority_queue<QWQ>
#define Rep(i, s, e, t) for(int i = (s); i <= (e); i += (t))
#define REP(i, s, e, t) for(int i = (s); i >= (e); i -= (t))
#define debug(a, b) cerr << #a << " = " << a << b;
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) (a) = ((a) < (b) ? (a) : (b))
#define Max(a, b) (a) = ((a) > (b) ? (a) : (b))
#define fi first
#define se second
#define int ll
using namespace std;
using ll = long long;
using ull = unsigned long long;il int read() { int a = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); } while('0' <= ch && ch <= '9') { a = a * 10 + ch - '0'; ch = getchar(); } return a * f; }
il void write(int x) { if(x < 0) putchar('-'), x = -x; if(x > 9) write(x / 10); putchar(x % 10 + '0'); }const int N = 1e5 + 10;
int n, k, _[N], ans = 0;struct AI {int x, r, q, L, R;  // x: 离散化后的位置, r: 视野半径, q: 智商, [L,R]: 离散化后的可见区间
} a[N];// 按视野半径降序排序,r 大的在前
il bool cmp(AI i, AI j) { return i.r > j.r; }
// 按智商升序排序,用于 CDQ 合并
il bool cmp1(AI i, AI j) { return i.q < j.q; }// 树状数组,维护位置上的机器人数量
struct BIT{int no[N];#define lowbit(x) (x & -x)il void Modify(int x, int val) { Rep(i, x, n, lowbit(i)) no[i] += val; }il int Query1(int x) { int sum = 0; REP(i, x, 1, lowbit(i)) sum += no[i]; return sum; }il int Query2(int l, int r) { return Query1(r) - Query1(l - 1); }
} bit;/** CDQ 分治解决偏序问题* 当前区间 [l, r] 已经按视野半径 r 降序排列(物理顺序)* 因此左半部分机器人的 r 均大于等于右半部分机器人的 r* 递归处理左右两部分后,统计跨区间贡献:*   对于每个在右半部分(r 较小)的机器人 i,它能看到对方要求距离 <= r_i*   统计左半部分(r 较大)中满足智商差 <= k 且位置在 a[i].L..a[i].R 内的机器人个数*   这些机器人对都会相互交谈(因为右边的能看到左边,左边 r 更大肯定也能看到右边)* 智商条件通过双指针维护:智商在 [a[i].q - k, a[i].q + k] 范围内的左半部分机器人会被加入树状数组* 位置条件通过树状数组查询区间和完成*/
il void CDQ(int l, int r) {if(l == r) return ;int mid = l + r >> 1;CDQ(l, mid);          // 递归左半CDQ(mid + 1, r);      // 递归右半int L = l, R = l - 1; // L: 左半部分当前移出的位置, R: 左半部分当前加入的位置Rep(i, mid + 1, r, 1) {  // 遍历右半部分的每一个机器人// 维护智商下界:将左半部分中智商小于 a[i].q - k 的机器人从树状数组中删除while(!(mid < L) && a[i].q - a[L].q > k) bit.Modify(a[L++].x, -1);// 维护智商上界:将左半部分中智商不超过 a[i].q + k 的机器人加入树状数组while(R < mid && !(a[R + 1].q - a[i].q > k)) bit.Modify(a[++R].x, 1);// 查询左半部分当前在树状数组中的机器人,有多少个位置落在 a[i].L 到 a[i].R 之间ans += bit.Query2(a[i].L, a[i].R);}// 清空树状数组,为上一层 CDQ 准备Rep(i, L, R, 1) bit.Modify(a[i].x, -1);// 将当前区间 [l, r] 按智商重新排序,使上一层合并时左右两部分智商有序sort(a + l, a + r + 1, cmp1);
}signed main() {n = read(), k = read();// 读入每个机器人的原始坐标、视野半径、智商Rep(i, 1, n, 1) _[i] = a[i].x = read(), a[i].r = read(), a[i].q = read();// ---- 离散化坐标 ----sort(_ + 1, _ + n + 1);int lo = unique(_ + 1, _ + n + 1) - _ - 1;  // lo 是不同坐标的个数Rep(i, 1, n, 1) {// 计算可见区间离散化后的左右端点// a[i].x - a[i].r 在离散化数组中的下标(第一个 >= 该值的位置)a[i].L = lower_bound(_ + 1, _ + lo + 1, a[i].x - a[i].r) - _;// a[i].x + a[i].r 在离散化数组中的下标(最后一个 <= 该值的位置)a[i].R = upper_bound(_ + 1, _ + lo + 1, a[i].x + a[i].r) - _ - 1;// 将机器人自身坐标也离散化a[i].x = lower_bound(_ + 1, _ + lo + 1, a[i].x) - _;}// 按视野半径降序排序,这样后续 CDQ 中左部分的 r 始终 >= 右部分的 rsort(a + 1, a + n + 1, cmp);CDQ(1, n);write(ans);return (1 ^ 0 ^ 1);  // 等效于 return 0;
}
http://www.jsqmd.com/news/976932/

相关文章:

  • 给开发者的‘反增长’手册:当你的代码效率提升40%,为何服务器负载反而翻倍了?
  • RAG 2.0:基于LangGraph的实时数据流增强生成架构
  • 别再傻傻分不清!AD20里原理图库、封装库和集成库到底怎么用?附实战避坑指南
  • Mac Mouse Fix:如何让10美元鼠标在macOS上实现超越苹果触控板的极致体验?
  • 2026湖北林业白蚁防治服务商盘点:古树名木生态防治机构解析 - 新闻快传
  • BilibiliCommentScraper:基于Selenium的B站全量评论数据采集方案
  • 你的文献库,可以像游戏一样有趣:Zotero-Style插件深度体验
  • GPT-4的1.8万亿参数与2%激活:MoE稀疏性真相解析
  • 2026年温州AI搜索优化公司实力深度评测与商业盈利选型指南 - 品牌报告
  • 2026年液压机源头厂家推荐榜单,大吨位/伺服/快速/龙门液压机,精密专机品牌实力深度解析 - 企业推荐官【官方】
  • 从四个参数学习 Chord Edit
  • 5分钟实现通达信缠论自动化:告别手动画线,让AI帮你分析股票走势
  • 3步掌握pywencai Cookie配置:高效获取同花顺问财数据的专业级解决方案
  • 2026春《编译原理》笔记
  • 除了weixin://wxpay,还有哪些小程序场景能用自定义协议生成二维码?一个思路拓展
  • 别再死记硬背了!一张图+五个生活比喻,彻底搞懂DFS、BFS、Dijkstra这些图算法
  • Proteus仿真必备技能:从‘NET=P#’到总线连接,彻底搞懂网络标号的自动标注逻辑
  • 【收藏】2026 年完整版大模型学习路线!零基础 / 程序员转行必看,从入门到项目落地全指南
  • 跟着 MDN 学JavaScript day_12:实战挑战——构建交互式笑话生成器
  • PN7160 NFC天线匹配实战:从原理到调优,解决通信距离与稳定性难题
  • Agent记忆系统:基于LangChain的Memory开发实战
  • GPT-4四大能力跃迁:从指令遵循到跨模态推理的工程实证
  • 计算机小程序毕设实战-基于springboot+微信小程序的云浮市特色农产品交易的设计与实现java 特色农产品销售系统 特色农产品线上交易【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 2026 南京梅雨季漏水抢修指南!本地防水公司 TOP9 权威盘点,卫生间免砸砖防水、阳台渗漏一站式解决 - 吉林同城获客
  • 在Windows上用Anaconda+TensorFlow 2.x复现U-Net细胞分割(附完整代码与数据集)
  • 2026年锻压机品牌/源头厂家最新推荐榜:半轴、轨道、道岔、螺栓、汽配、航空、航天、军品、船舶锻压机/自由锻/三向锻高强智造精选 - 企业推荐官【官方】
  • 超自动化运维:实现IT服务管理现代化的关键
  • pyltp加载自定义词典踩坑实录:解决专业术语(如‘亚硝酸盐’)分词不准的问题
  • Text-to-X多模态系统实战:从文本指令到PPT/视频/试题一键生成
  • GEO优化对搜索关键词有要求吗