CDQ分治学习笔记
CDQ 分治是一种离线的分治思想,可以用来处理以下问题:
- 解决和点对有关的问题。
- 1D 动态规划的优化与转移。
- 通过 CDQ 分治,将一些动态问题转化为静态问题。
(摘自 oiwiki)
解决和点对有关的问题
算法流程如下:
要解决 \([l, r]\) 的问题,先将 \([l, mid],[mid + 1, r]\) 的问题解决,再解决整个区间的问题,所以不严格得讲,分治求平面最近点对、归并排序都是 CDQ 分治的思想(但是 CDQ 分治一般会套上数据结构)。
例:三维偏序
我们要求 \([1,n]\) 中的点对 \((i,j)\),先分治,解决 \([1,mid],[mid+1,n]\) 的子问题,再考虑 \(l \le i \le mid, mid + 1 \le j \le r\) 的点对,我们先在一开始让数组按 \(a_i\) 升序排序,保证 \(\forall l \le i \le mid < j \le r, a_j \ge a_i\),然后再左右分别按 \(b_i\) 升序排序,这是为了方便我们双指针找到所有满足 \(b_i \le b_j\) 的 \(i\),接下来就不用多说了,用树状数组把 \(c_i\) 存起来然后计算即可。
注意:cmp1 不仅要按 \(a_i\) 排,还要在 \(a_i\) 相同时按 \(b_i,c_i\) 排,否则可能会让满足条件的点对 \((i,j)\) \(i\) 在右边而统计不到, cmp2 则不用(但建议还是写一下)
例2:[CQOI2011] 动态逆序对
记 \(t_i\) 为 \(i\) 被删除的时间,如果没有被删除则 \(t_i = +\infty\),然后考虑一个点 \(i\) 删除前后,删除后少去了其本身当前的贡献,也就是点对 \((i,j)\) 满足 \(t_i>t_j \land ((i < j \land val_i>val_j) \lor (i > j \land val_i < val_j))\),这就转化成了一个三维偏序问题。
优化动态规划的转移
其实和点对之间的关系的处理是同理的,但是要注意的是,处理跨越区间 (\(i<mid<j\))的问题时,要按照正确的 dp 转移顺序来。例如,我们有一个二维的 LIS 问题,转移方程为:
\(dp_i=\max\limits_{j=1}^{i-1} [a_j<a_i \land b_j<b_i]dp_j\)
这里我们之间一个 cdq 分治上去,树状数组维护前缀最大值就能优化到 \(O(n \log^2 n)\), 但是中间的处理部分必须要放在 cdq(l, mid) 和 cdq(mid+1, r) 之间,因为在转移 \(i\) 的时候要求 \(j<i\) 的 \(dp_j\) 都被处理完了。如果 \(l=r\), 则说明 \(dp_l\) 处理完毕了,直接 return 即可。
例:[SDOI2011] 拦截导弹
如上,第一问跑一个二维最长非升子序列即可,但是第二问,我们需要求出存在 \(i\) 的最长非升子序列个数,除以总个数,这里有个 trick,只要求出以 \(i\) 结尾、开头的最长非升子序列个数,再相乘即可(如果两者拼起来的长度为全局最长长度的话),那么目标明确,两边 \(CDQ\) 就行,细节有点多,可以慢慢理解下代码。
将动态问题转化为静态问题
这里,CDQ 分治折半的是 时间序列,也可以说是操作(修改和询问)顺序序列,常用于解决 修改 xxx 询问 xxx 的问题,但是这是个离线算法,我们要先把所有修改、询问存下来,这样操作本身就按时间排好序了,这时,每个修改都会影响到后面的询问,这样的点对共有 O(n^2) 对。称为一个修改-询问关系,下面我们用 \(i\) 来表示第 \(i\) 次操作。
接下来思路便明了了,我们在解决 \((l, r)\) 中所有修改和询问问题时,先处理 \((l, mid), (mid + 1, r)\) 之间的关系,再处理 \((i, j)(l \le i \le mid \le j \le r)\) 的关系,其中 \(i\) 是一个修改,\(j\) 是一个询问。
有一点要注意的是,如果修改之间相互 \(独立\), 比如区间加减什么的,这时候我们随便什么时候处理

浙公网安备 33010602011771号