#edu3004. 【教程】CDQ 分治

【教程】CDQ 分治

Update: 2026.4.30

CDQ 分治是一种分治算法,或者是一种解决问题的思想。其核心思想就是: 将序列递归分成两个子区间,每个问题只处理左区间的修改对右区间的贡献

CDQ 分治需要对多个维度按照一定规律进行排序,这就要求离线使用,也就是问题本身只能离线才能处理。

CDQ 分治可以解决三类问题

  • 1.与点对有关的问题(数点或者偏序问题)
  • 2.优化 1D/1D 动态规划的转移(与偏序有关)
  • 3.将带修改 & 查询的动态问题,转化静态问题 (第一维度是时间)

本质上,这些问题都属于“点对之间的贡献问题”。

常见时间复杂度

T(n)=2T(n/2)+O(n)O(nlogn)T(n)=2T(n/2)+O(n) \longrightarrow O(n\log{n})

$T(n)=2T(n/2)+O(n\log{n})\longrightarrow O(n\log^2{n})$

分治的两种顺序

  1. 递归左区间 [l,mid] \to 递归右区间 [l,mid] \to 双指针解决左区间修改对右区间贡献
  2. 递归左区间 [l,mid] \to 双指针解决左区间修改对右区间贡献\to 递归右区间 [l,mid]

有何差别?

  1. 第一类问题(点对关系)两种方式都可以。

  2. 有时序依赖的问题,第二类(1D/1D 动态规划)和部分第三类问题(单点修改有前后依赖),必须采用第二种分治策略。


一、点对有关问题

例1. 【模板】二维偏序

【参考代码】

  1. O(nlog2n)O(n\log^2{n}) 写法:点击
  2. O(nlogn)O(n\log{n}) 写法:点击

例2. 【模板】三维偏序

本题加强版(点有重复)P3810三维偏序

【分析】luogu的这道模板题,点有重复,需要处理好点重复这个细节问题。

例3.【模板】四维偏序

【分析】CDQ 套 CDQ

【参考代码】点击


二、优化 1D / 1D 动态规划的转移

【注意】由于转移是有顺序,CDQ 必须才有中序遍历方式。

例4. 【模版】一维最长上升子序列

【分析】一维 LIS 问题,可以使用二分、数据结构解决。这里介绍 CDQ 优化

【参考代码】点击

例5.【模板】二维最长上升子序列

【分析】要保持原序列顺序,那么就要满足三维性质:

idj<idi,aj<ai,bj<biid_j<id_i,a_j<a_i,b_j<b_i

idid 进行分治,CDQ 考虑第二维,利用数据结构维护第三维度。

注意:要使用中序遍历方式。

【参考代码】点击

例6. P4093[HEOI2016/TJOI2016] 序列

【分析】若 mnimn_i 表示 aia_i 能够变到的最小值,mximx_i 表示能变到的最大值,fif_i 表示以 ii 结尾的最长子序列长度,则转移方程:

fj=1+maxifif_j=1+\max_i {f_i}

其中 ii 满足:

  • idi<idjid_i<id_j

  • ximnjx_i\le mn_j

  • mxixjmx_i \le x_j

    idid 分治,解决第一维。 左区间按照 xx 排序,右区间按照 mnmn 排序,然后双指针解决第二维。树状数组维护第三维。

【参考代码】点击

例7. P3364 Cool loves touli

【分析】 l,s,w,al,s,w,a 分别表示等级、力量、智力、攻击力

fif_i 表示以 ii 结尾最长上升子序列,可以得到转移:

fj=1+maxfif_j=1+\max{f_i}

其中 ii 满足

  • li<ljl_i < l_j
  • aisja_i \le s_j
  • wiajw_i \le a_j

对等级 l 分治,cdq 内部左区间对 aa 进行排序,右区间对 ss 排序,第三维 wiajw_i \le a_j 用树状数组维护。

需要对 wiw_iaja_j 离散化,由于 ssaa 有大小比较,那么 a,s,wa,s,w 都需要离散化。

例8. P5621 [DBOI2019] 德丽莎世界第一可爱

【分析】ii 转移到 jj 需要满足四个属性,类似于 P3769 TATT 可以 KD-tree 或 CDQ 解决

按照其中一维进行分治,剩余三个维度可以 CDQ 套 CDQ 进行。


三、动态转静态

所谓动态转静态,本质上就是为所有操作额外加了一个属性 —— 时间戳 timtim

ii 能对 jj 产生贡献,当且仅当:

  • timi<timjtim_i​ < tim_j

  • ii 是修改操作

  • jj 是查询操作

【注意】如果修改有时序依赖关系,那么必须采用中序遍历。

例8. P4390 [BalkanOI 2007] Mokia 摩基亚

【分析】动态二维数点,K-D tree 和 CDQ 分治都能解决

例9. P4169 [Violet] 天使玩偶/SJY摆棋子

本题有若干操作,动态插入一些点,询问某个点距离最近的点的曼哈顿距离。

暂时忽略掉插入新点操作,查询 (x,y)(x,y) 最近的点的距离,答案就是: min{xxi+yyi}min\{|x-x_i|+|y-y_i|\} , 为了去掉绝对值,我们将询问分成 44 类,分别询问左下、左上、右下、右上方向上距离最近的点的距离,保留最小的就是答案。

  • 左下:min{(xxi)+(yyi)}=(x+y)max{xi+yi}min\{(x-x_i)+(y-y_i)\}=(x+y)-max\{x_i+y_i\}
  • 左上:min{(xxi)+(yiy)}=(xy)max{xiyi}min\{(x-x_i)+(y_i-y)\}=(x-y)-max\{x_i-y_i\}
  • 右下:min{(xix)+(yyi)}=(x+y)max{xi+yi}min\{(x_i-x)+(y-y_i)\}=(-x+y)-max\{-x_i+y_i\}
  • 右上:min{(xix)+(yiy)}=(xy)max{xiyi}min\{(x_i-x)+(y_i-y)\}=(-x-y)-max\{-x_i-y_i\}

我们可以将 nn 个点的坐标和 mm 个询问的坐标按照横坐标从小到大排序。然后依次扫描。

对于左下方来说:

  1. 若扫描到一个点 (xi,yi)(x_i,y_i) ,则在树状数组第 yiy_i 个位置上的值与 xi+yix_i+y_i 取最大,同时维护前缀最大。
  2. 如果扫描到了一个查询 (x,y)(x,y) ,则在树状数组,则在树状数组(或线段树)中查询 [1,y][1,y] 的最大 valval ,该询问就是 x+yvalx+y-val.

上面的做法,排序保证了 xi<xx_i<x 的条件,树状数组控制 yi<yy_i<y 的条件并维护了 xi+yix_i+y_i 的最大值。

对于左上、右上和左下与上述方法类似,例如左上方,还是按照横坐标排序,这时应该是 xix,106yi106yx_i≤x,10^6−y_i≤10^6−y,因此树状数组改成维护 xiyix_i−y_i 的最大值,修改时的位置变为 106yi+110^6−yi+1,查询时的前缀变为 [1,106yi+1][1,10^6-y_i+1],剩下两个方向可自行推导。

到这里我们再加上加入操作,除了查询还可能在平面中添加点。有查询和插入操作的动态问题,这就可以用 CDQ 分治分成若干个静态小问题来解决。

那么我们每次递归完左、右区间之后还需要处理左区间修改对右区间查询的影响。可以把第 lmidl \sim mid 项操作中增加的点依次加入一个初始为空的平面,再考虑第 mid+1rmid+1∼r 项操作中的查询,找到平面上距离最近的点来更新答案,可以用和上面一样的做法。

注意!为了保证每次的时间复杂度之和当前递归的区间内部相关,因此每次不能建立一个新的树状数组,必须在处理完每个区间后再将对树状数组的修改依次还原,保证每次进入和离开时,树状数组都是空的,可以复用。这样整个算法的时间复杂度为 O((n+m)log22(n+m))O((n + m) \log_2^2(n + m))

注意! 本题会卡常,需要在 CDQ 分治时进行一些优化,如果区间中只存在添加操作,不存在查询操作,那么不需要处理左区间的修改操作对右区间的查询操作的影响。这是一步效果非常明显的优化(不加这步就只能吸氧了)

【参考代码】点击

学习完毕

{{ select(1) }}

  • YES
  • NO