本页面将介绍 CDQ 分治。

简介

CDQ 分治是一种思想而不是具体的算法,与 动态规划 类似。目前这个思想的拓展十分广泛,依原理与写法的不同,大致分为三类:

  • 解决和点对有关的问题。
  • 1D 动态规划的优化与转移。
  • 通过 CDQ 分治,将一些动态问题转化为静态问题。

CDQ 分治的思想最早由 IOI2008 金牌得主陈丹琦在高中时整理并总结,它也因此得名。1

解决和点对有关的问题

这类问题多数类似于「给定一个长度为 的序列,统计有一些特性的点对 的数量」或「给定一个长度为 的序列,找到一对点 使得一些函数的值最大」。

CDQ 分治解决这类问题的算法流程如下:

  1. 找到这个序列的中点

  2. 将所有点对 划分为 3 类:

    1. 的点对;
    2. 的点对;
    3. 的点对。
  3. 这个序列拆成两个序列 。此时第一类点对和第三类点对都在这两个序列之中;

  4. 递归地处理这两类点对;

  5. 设法处理第二类点对。

可以看到 CDQ 分治的思想就是不断地把点对通过递归的方式分给左右两个区间。

在实际应用时,我们通常使用一个函数 solve(l,r) 处理 的点对。上述算法流程中的递归部分便是通过 solve(l,mid)solve(mid,r) 来实现的。剩下的第二类点对则需要额外设计算法解决。

例题

给定一个序列,每个点有 三个属性,试求:这个序列里有多少对点对 满足

对于序列 ,它的逆序对数定义为集合 中的元素个数。

现在给出 的一个排列,按照某种顺序依次删除 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。

CDQ 分治优化 1D/1D 动态规划的转移

相关内容:[CDQ 分治优化 DP](https://leetcode.com/problems/DP 优化#cdq-分治优化-dp/)

1D/1D 动态规划指的是一类特定的 DP 问题,该类题目的特征是 DP 数组是一维的,转移是 的。如果条件良好的话,有时可以通过 CDQ 分治来把它们的时间复杂度由 降至

例如,给定一个序列,每个元素有两个属性 。我们希望计算一个 DP 式子的值,它的转移方程如下:

这是一个二维最长上升子序列的 DP 方程,即只有 , 的点 可以更新点 的 DP 值。

直接转移显然是 的。以下是使用 CDQ 分治优化转移过程的讲解。

我们发现 转移到 这种转移关系也是一种点对间的关系,所以我们用类似 CDQ 分治处理点对关系的方式来处理它。

这个转移过程相对来讲比较套路。假设现在正在处理的区间是 ,算法流程大致如下:

  1. 如果 ,说明 值已经被计算好了。直接令 然后返回即可;
  2. 递归使用 solve(l,mid)
  3. 处理所有 的转移关系;
  4. 递归使用 solve(mid+1,r)

第三步的做法与 CDQ 分治求三维偏序差不多。处理 的转移关系的时候,我们会发现已经不用管 这个限制条件了。因此,我们依然先将所有的点 和点 值进行排序处理,然后用双指针的方式将 点插入到树状数组里,最后查一下前缀最大值更新一下 就可以了。

转移过程的正确性证明

该 CDQ 写法和处理点对间关系的 CDQ 写法最大的不同就是处理 的点对这一部分。处理点对间关系的 CDQ 写法中,这一部分放到哪里都是可以的。但是,在用 CDQ 分治优化 DP 的时候,这个流程却必须夹在 , 的中间。原因是 DP 的转移是 有序的,它必须满足两个条件,否则就是不对的:

  1. 用来计算 的所有 值都必须是已经计算完毕的,不能存在「半成品」;

  2. 用来计算 的所有 值都必须能更新到 ,不能存在没有更新到的 值。

上述两个条件可能在 暴力的时候是相当容易满足的,但是使用 CDQ 分治后,转移顺序很显然已经乱掉了,所以有必要考察转移的正确性。

CDQ 分治的递归树如下所示。

执行刚才的算法流程的话,以 这个点为例,它的 DP 值是在 solve(1,8)solve(5,8)solve(7,8) 这 3 个函数中更新完成的,而三次用来更新它的点分别是 这三个不相交的区间;又以 这个点为例,它的 DP 值是在 solve(1,4) 函数中解决的,更新它的区间是 。仔细观察就会发现,一个 点的 DP 值被更新了 次,而且,更新它的区间刚好是 在线段树上被拆分出来的 个区间。因此,我们的确保证了所有合法的 都更新过点 ,满足第 2 个条件。

接着分析我们算法的执行流程:

  1. 第一个结束的函数是 solve(1,1)。此时我们发现 的值已经计算完毕了;
  2. 第一个执行转移过程的函数是 solve(1,2)。此时我们发现 的值已经被转移好了;
  3. 第二个结束的函数是 solve(2,2)。此时我们发现 的值已经计算完毕了;
  4. 接下来 solve(1,2) 结束, 这段区间的 值均被计算好;
  5. 下一个执行转移流程的函数是 solve(1,4)。这次转移结束之后我们发现 的值已经被转移好了;
  6. 接下来结束的函数是 solve(3,3)。我们会发现 的 dp 值被计算好了;
  7. 接下来执行的转移是 solve(2,4)。此时 solve(1,4) 中被 转移了一次,这次又被 转移了,因此 的值也被转移好了;
  8. solve(4,4) 结束, 的值计算完毕;
  9. solve(3,4) 结束, 的值计算完毕;
  10. solve(1,4) 结束, 的值计算完毕。
  11. ……

通过模拟函数流程,我们发现一件事:每次 solve(l,r) 结束的时候, 区间的 DP 值会被全部计算好。由于我们每一次执行转移函数的时候,solve(l,mid) 已经结束,因此我们每一次执行的转移过程都是合法的,满足第 1 个条件。

在刚才的过程我们发现,如果将 CDQ 分治的递归树看成一颗线段树,那么 CDQ 分治就是这个线段树的 中序遍历函数,因此我们相当于按顺序处理了所有的 DP 值,只是转移顺序被拆开了而已,所以算法是正确的。

例题

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度、并且能够拦截任意速度的导弹,但是以后每一发炮弹都不能高于前一发的高度,其拦截的导弹的飞行速度也不能大于前一发。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

在不能拦截所有的导弹的情况下,我们当然要选择使国家损失最小、也就是拦截导弹的数量最多的方案。但是拦截导弹数量的最多的方案有可能有多个,如果有多个最优方案,那么我们会随机选取一个作为最终的拦截导弹行动蓝图。

我方间谍已经获取了所有敌军导弹的高度和速度,你的任务是计算出在执行上述决策时,每枚导弹被拦截掉的概率。

将动态问题转化为静态问题

前两种情况使用 CDQ 分治的目的是将序列折半之后递归处理点对间的关系,来获得良好的复杂度。不过在本节中,折半的不是一般的序列,而是时间序列。

它适用于一些「需要支持做 xxx 修改然后做 xxx 询问」的数据结构题。该类题目有两个特点:

  • 如果把询问 离线,所有操作会按照时间自然地排成一个序列。
  • 每一个修改均与之后的询问操作息息相关。而这样的「修改 - 询问」关系一共会有 对。

我们可以使用 CDQ 分治对于这个操作序列进行分治,处理修改和询问之间的关系。

与处理点对关系的 CDQ 分治类似,假设正在分治的序列是 , 我们先递归地处理 之间的修改 - 询问关系,再处理所有 的修改 - 询问关系,其中 是一个修改, 是一个询问。

注意,如果各个修改之间是 独立 的话,我们无需处理 ,以及 solve(l,mid)solve(mid+1,r) 之间的时序关系(比如普通的加减法问题)。但是如果各个修改之间并不独立(比如说赋值操作),做完这个修改后,序列长什么样可能依赖于之前的序列。此时处理所有跨越 mid 的修改 - 询问关系的步骤就必须放在 solve(l,mid)solve(mid+1,r) 之间。理由和 CDQ 分治优化 1D/1D 动态规划的原因是一样的:按照中序遍历序进行分治才能保证每一个修改都是严格按照时间顺序执行的。

例题

矩形加矩形求和

维护一个二维数组,支持在一个矩形区域内加一个数字,每次询问一个矩形区域的和。

维护一个长为 的序列 ,有 次操作。

  1. 将区间 的值修改为
  2. 询问区间 出现了多少种不同的数,也就是说同一个数出现多次只算一个。

一句话题意:区间赋值区间数颜色。

PS 国是一个拥有诸多城市的大国。国王 Louis 为城市的交通建设可谓绞尽脑汁。Louis 可以在某些城市之间修建道路,在不同的城市之间修建道路需要不同的花费。

Louis 希望建造最少的道路使得国内所有的城市连通。但是由于某些因素,城市之间修建道路需要的花费会随着时间而改变。Louis 会不断得到某道路的修建代价改变的消息。他希望每得到一条消息后能立即知道使城市连通的最小花费总和。Louis 决定求助于你来完成这个任务。

一句话题意:给定一张图支持动态的修改边权,要求在每次修改边权之后输出这张图的最小生成树的最小代价和。

参考资料与注释

Footnotes

  1. 从《Cash》谈一类分治算法的应用