CDQ分治与斜率优化DP——学习笔记

本文介绍了一种使用CDQ分治技巧优化动态规划的方法,通过将动态问题转化为静态问题,实现对斜率优化DP中非单调x坐标的有效处理。此方法避免了复杂的splay树维护,简化了编程难度。

我们知道当斜率优化DP中的点的x坐标不单调时,需要splay来维护凸壳,但是代码量比较大,容易写挂。
我们还有一种神奇的做法:CDQ分治。
先把n个状态排成一个序列。考虑一个分治过程solve(L,R),每次分成[L,mid],[mid+1,R]两部分。
显然对于fi只和1~i-1有关,所以我们solve(L,mid)递归求解,即可得到[L,mid]的所有f值。
现在我们需要考虑用[L,mid]中求出的值去更新[mid+1,R]中的待求的f:
这个子问题已经是静态的了,然后我们就可以排序了。
对于[L,mid]中的所有决策点按x排序,然后就可以线性构造出这个凸壳。
再对[mid+1,R]中的状态按斜率排序。这样又能线性在凸壳中找到最优点。是不是很妙啊。
总复杂度O(Nlog^2N)。虽然多了一个log, 但是编程复杂度降低了很多,对于写不动大数据结构的老年人选手是很好的选择。
CDQ分治的这种动态转静态的思想特别好,许多数据结构题都适用。

<think>我们正在讨论动态规划中的斜率优化技术。斜率优化主要用于优化形如 $dp[i] = \min_{j < i}\{ dp[j] + f(i, j) \}$ 的转移方程,其中 $f(i, j)$ 可以分解为关于 $i$ 和 $j$ 的函数的组合。斜率优化的核心思想是将转移方程转化为直线方程,利用决策点的斜率单调性,通过维护一个凸包(上凸包或下凸包)来快速找到最优决策点。 根据引用内容,斜率优化通常适用于以下情况: 1. 转移方程可以写成:$dp[i] = \min_{j < i} \{ dp[j] + A(i) \cdot B(j) + C(i) + D(j) \}$,其中 $A(i)$ 和 $B(j)$ 是分别只 $i$ 和 $j$ 有关的函数。 2. 通过代数变形,将转移方程转化为:$dp[j] + D(j) = -A(i) \cdot B(j) + (dp[i] - C(i))$。这样,我们可以将每个决策点 $j$ 看作二维平面上的一条斜率为 $-A(i)$ 的直线,截距为 $dp[i]-C(i)$,而每个决策点 $j$ 对应于点 $(B(j), dp[j]+D(j))$。 3. 当 $A(i)$ 具有单调性(例如递增)时,我们可以利用单调队列维护凸包;当不具有单调性时,可能需要二分查找。 具体步骤: 1. **变形方程**:将转移方程变形,使得只 $j$ 相关的项($dp[j]+D(j)$ 和 $B(j)$)作为点的坐标,而只 $i$ 相关的项($A(i)$)作为斜率。 2. **维护凸包**:根据斜率的变化趋势(递增或递减)维护一个凸包(上凸或下凸)。通常,如果斜率是递增的,则维护下凸包(最小值);如果斜率是递减的,则维护上凸包(最大值)。但具体问题需要具体分析。 3. **寻找最优决策点**:在凸包上,最优决策点满足它前后的点所连成的直线的斜率当前斜率 $k_i$ 满足一定的关系(例如,对于下凸包,最优决策点满足:前一个点当前点的斜率小于 $k_i$,而当前点后一个点的斜率大于等于 $k_i$)。 4. **加入新决策点**:在计算完当前状态 $dp[i]$ 后,将点 $(B(i), dp[i]+D(i))$ 加入凸包,同时维护凸包性质(删除破坏凸性的点)。 引用[1]中给出了一个例子:凸包上的斜率呈单增态,那么最优决策点就是第一个斜率大于等于 $k_0$ 的线段的左端点。由于斜率单调递增,可以使用二分查找。 引用[4]提到,当右边是一个递增的变量(即斜率单调递增)时,会选用斜率递增的单调队列(上凸包或下凸包,具体问题具体分析)。在引用[4]的图中,斜率递增,因此维护的是上凸包?但注意:通常下凸包对应最小值问题,上凸包对应最大值问题。但具体问题需要分析。 下面我们通过一个经典问题“任务安排”来具体说明(参考引用[5]的问题): 问题描述:有 $n$ 个任务,每个任务需要 $t_i$ 的时间,费用系数为 $c_i$。任务可以分批,每批任务有一个启动时间 $S$。每批任务连续完成。设第 $i$ 个任务完成的时间为 $T_i$,则其费用为 $T_i \times c_i$。总费用为所有任务费用之和。要求最小化总费用。 状态定义:设 $dp[i]$ 表示完成前 $i$ 个任务的最小费用。 转移方程:考虑将 $j+1$ 到 $i$ 的任务分成一批,则: $$ dp[i] = \min_{0 \leq j < i} \left\{ dp[j] + \text{sumT}_i \times (\text{sumC}_i - \text{sumC}_j) + S \times (\text{sumC}_n - \text{sumC}_j) \right\} $$ 其中,$\text{sumT}_i = \sum_{k=1}^i t_k, \text{sumC}_i = \sum_{k=1}^i c_k$。 变形:将只 $j$ 相关的项提出来: $$ dp[i] = \min_{j} \left\{ dp[j] - (S + \text{sumT}_i) \times \text{sumC}_j \right\} + \text{sumT}_i \times \text{sumC}_i + S \times \text{sumC}_n $$ 令 $A(i) = S + \text{sumT}_i$,$B(j) = \text{sumC}_j$,$C(i) = \text{sumT}_i \times \text{sumC}_i + S \times \text{sumC}_n$,$D(j) = 0$,则: $$ dp[i] = \min_{j} \left\{ dp[j] - A(i) \cdot B(j) \right\} + C(i) $$ 移项: $$ dp[j] = A(i) \cdot B(j) + (dp[i] - C(i)) $$ 这里,$A(i)$ 是递增的(因为 $\text{sumT}_i$ 递增),$B(j)$ 也是递增的($\text{sumC}_j$ 递增)。 因此,每个决策点 $j$ 对应于点 $(B(j), dp[j])$。我们要求的是斜率为 $A(i)$ 的直线过某个决策点 $j$ 时的截距($dp[i]-C(i)$)最小。所以,我们维护一个下凸包(因为要求最小值,且斜率 $A(i)$ 递增)。 维护凸包步骤: 1. 在队列中,相邻点之间的斜率递增(下凸包)。 2. 当计算 $dp[i]$ 时,因为斜率 $A(i)$ 递增,所以队首的决策点可能不再是最优(因为后面的点可能更优),我们需要检查队首两个点之间的斜率是否小于 $A(i)$,如果小于,则队首点不是最优,弹出队首,直到队首两点斜率大于等于 $A(i)$。此时队首点即为最优决策点。 3. 计算 $dp[i]$。 4. 将点 $(B(i), dp[i])$ 加入队尾。在加入之前,需要检查队尾三个点是否保持凸性(即队尾三个点中,中间点是否在下面,如果不是则弹出队尾点,直到满足凸性)。 具体实现: - 使用双端队列(deque)存储决策点的下标(或者直接存储坐标)。 - 队列中存储的决策点对应凸包上的点。 伪代码: ```python 初始化:dp[0] = 0, 队列q,将0加入队列 for i in range(1, n+1): # 从队首弹出斜率小于当前斜率A(i)的点(因为斜率递增,所以队首的点可能不再是最优) while len(q) >= 2 and slope(q[0], q[1]) <= A(i): pop_front(q) j = q.front() # 最优决策点 dp[i] = dp[j] - A(i)*B(j) + C(i) # 将点i加入队列,维护凸包 while len(q) >= 2 and slope(q[-1], i) <= slope(q[-2], q[-1]): pop_back(q) push_back(q, i) ``` 其中,`slope(j, k)` 表示点j和点k之间的斜率:$(dp[k]-dp[j]) / (B(k)-B(j))$。 注意:这里假设 $B(k) \neq B(j)$,且 $B$ 是递增的,所以分母不为0。 通过以上步骤,我们可以在 $O(n)$ 的时间复杂度内完成动态规划。 总结:斜率优化动态规划的关键在于将转移方程变形为直线方程,利用决策点的凸性,通过维护凸包来快速寻找最优决策点。 §§相关问题§§ 1. 斜率优化动态规划适用于哪些类型的转移方程? 2. 如何判断一个动态规划问题是否可以使用斜率优化? 3. 斜率优化中,如何维护凸包? 4. 斜率优化中,当斜率不单调时,如何寻找最优决策点? 5. 能否举例说明一个斜率优化动态规划的具体问题?</think>### 动态规划中的斜率优化技术 斜率优化是动态规划的高级优化技术,用于**寻找最优决策点**并降低时间复杂度。其核心思想是将状态转移方程转化为几何形式(直线斜率),通过维护**决策点的凸包**(上凸包或下凸包)快速定位最优解。以下是关键技术要点: --- #### 一、斜率优化的适用条件 1. **状态转移方程形式** 需满足:$dp[i] = \min_{j < i} \\{ dp[j] + f(i, j) \\}$ 其中 $f(i, j)$ 可拆分为:$f(i, j) = A(i) \cdot B(j) + C(i) + D(j)$ ($A(i), B(j)$ 分别为 $i$ 和 $j$ 的函数) 2. **斜率单调性** - 若 $A(i)$ 单调递增/递减,可用单调队列维护凸包($O(n)$) - 若 $A(i)$ 无单调性,需二分查找凸包($O(n \log n)$) --- #### 二、实现步骤(以最小化问题为例) 1. **变形状态转移方程** 将方程改写为: $$ \underbrace{dp[j] + D(j)}_{Y_j} = \underbrace{-A(i)}_{斜率} \cdot \underbrace{B(j)}_{X_j} + \underbrace{(dp[i] - C(i))}_{截距} $$ 此时每个决策点 $j$ 对应平面点 $(X_j, Y_j) = (B(j), dp[j] + D(j))$。 2. **维护凸包** - **下凸包**(最小值问题):相邻点斜率递增($\frac{Y_{k+1}-Y_k}{X_{k+1}-X_k} \geq \frac{Y_k-Y_{k-1}}{X_k-X_{k-1}}$) - **上凸包**(最大值问题):相邻点斜率递减 *示例凸包维护(引用[4]):* > 斜率递增的单调队列对应下凸包,通过删除队尾破坏凸性的点保证斜率单调性[^4]。 3. **寻找最优决策点** - **单调队列法**($A(i)$ 递增时): 1. 删除队首斜率 $\leq A(i)$ 的点(因其不会更优) 2. 队首即最优决策点 $j^*$ *示例(引用[1]):* > 当凸包斜率单增时,找到第一个满足 $\frac{Y_j-Y_{j-1}}{X_j-X_{j-1}} \geq A(i)$ 的点 $j$,其左端点即最优决策点[^1]。 - **二分法**($A(i)$ 无单调性): 在凸包上二分查找满足 $\frac{Y_j-Y_{j-1}}{X_j-X_{j-1}} \geq A(i)$ 的最小 $j$。 4. **更新状态决策点** 计算 $dp[i] = dp[j^*] + A(i) \cdot B(j^*) + C(i)$,并将点 $(B(i), dp[i] + D(i))$ 加入凸包。 --- #### 三、经典问题示例 **任务安排问题**(引用[5]): - **状态转移**:$dp[i] = \min_{j < i} \\{ dp[j] + (sumT_i + S) \cdot (sumC_i - sumC_j) \\}$ - **斜率优化**: - $A(i) = sumT_i + S$, $B(j) = sumC_j$ - 维护点 $(sumC_j, dp[j])$ 的下凸包 - 单调队列维护最优 $j$(因 $sumT_i$ 递增) *优化效果*:时间复杂度从 $O(n^3)$ 降至 $O(n)$[^5]。 --- #### 四、注意事项 1. **避免除零错误**:若 $X_j$ 可能相等,需特判(如直接比较 $Y_j$)。 2. **凸包方向**:最小值问题用下凸包,最大值问题用上凸包。 3. **决策单调性**:若 $B(j)$ 非单调,需使用平衡树/CDQ分治维护凸包。 > 核心优势:将动态规划中枚举决策的复杂度从 $O(n)$ 降至 $O(1)$ 或 $O(\log n)$,适用于大规模问题[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值