好的,这是根据您提供的目录大纲和要求为您撰写的 《C++算法竞赛攻略:从入门到降维打击》 的 第14章:区间DP、树形DP与其他 的完整内容。
第14章:区间DP、树形DP与其他
欢迎来到动态规划的进阶领域。在前两章,我们已经掌握了线性DP和背包DP,它们通常处理序列或集合问题。本章将介绍三种更特殊的DP模型,它们建立在特定的结构之上:区间DP处理“合并”与“分割”问题,树形DP在树状结构上进行状态转移,而状压DP则利用二进制位运算巧妙地处理小规模集合状态。掌握它们,你将能攻克更多结构性强的复杂问题。
14.1 区间DP模型与实现
1. 核心思想
区间DP,顾名思义,是在一个“区间”上进行动态规划。它通常用于解决一类可以被分解为子区间,并且子区间的解能够合并为父区间解的问题。
其鲜明特征是:
- 合并/分割:问题最终要求解整个序列(大区间)上的最优值,而这个最优值来自于其所有子区间的某个最优组合。例如,石子合并问题、括号匹配问题、矩阵链乘问题等。
- 状态表示:通常使用二维数组 dp[i][j]dp[i][j]dp[i][j] 表示区间 [i,j][i, j][i,j] 上的最优值。
- 递推顺序:DP的计算顺序不再是简单的从左到右。而是依赖于区间长度。我们总是先计算出所有长度为1的区间的值,然后是长度为2的,接着是长度为3的…直到最后计算出我们想要的、覆盖整个序列的区间 [1,N][1, N][1,N] 的值。
2. 区间DP解题模板
区间DP的实现代码具有高度的模式化,其骨架如下:
- 初始化 (Initialization):初始化长度为1的区间,即 dp[i][i]dp[i][i]dp[i][i] 的值。
- 枚举区间长度 (Enumerate Length):外层循环从 len=2len = 2len=2 到 NNN。
- 枚举区间起点 (Enumerate Start):中层循环枚举区间的左端点 iii,则右端点 j=i+len−1j = i + len - 1j=i+len−1。
- 枚举分割点 (Enumerate Split Point):内层循环在区间 [i,j][i, j][i,j] 中枚举分割点 kkk (KaTeX parse error: Undefined control sequence: \< at position 9: i \le k \̲<̲ j) 。
- 状态转移 (State Transition):根据分割点 kkk 将区间 [i,j][i, j][i,j] 分为 [i,k][i, k][i,k] 和 [k+1,j][k+1, j][k+1,j] 两部分,进行状态转移。
状态转移方程的一般形式为:
dp[i][j]=mini≤k<j/maxi≤k<j{
dp[i][k]+dp[k+1][j]+cost(i,j)}dp[i][j] = \min_{i \le k < j} / \max_{i \le k < j} \{ dp[i][k] + dp[k+1][j] + \text{cost}(i, j) \}dp[i][j]=i≤k<jmin/i≤k<jmax{
dp[i][k]+dp[k+1][j]+cost(i,j)}
其中 cost(i,j)\text{cost}(i, j)cost(i,j) 表示合并两个子区间的代价。
3. C++ 实现与经典例题:石子合并
问题描述:在一个圆形操场的四周摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的两堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。求将所有石子合并成一堆的最小得分。
分析:这是一个典型的区间DP问题。我们用 dp[i][j]dp[i][j]dp[i][j] 表示合并区间 [i,j][i, j][i,j] 的石子堆所需的最小代价。为了合并区间 [i,j][i, j][i,j],我们必然是先将某个子区间 [i,k][i, k][i,k] 合并成一堆,再将子区间 [k+1,j][k+1, j][k+1,j] 合并成一堆,最后将这两堆合并。
- 状态表示:dp[i][j]dp[i][j]dp[i][j] 表示合并第 iii 堆到第 jjj 堆石子的最小代价。
- 状态计算:dp[i][j]=mini≤k<jdp[i][k]+dp[k+1][j]+∑l=ijstones[l]dp[i][j] = \min_{i \le k < j} { dp[i][k] + dp[k+1][j] + \sum_{l=i}^{j} \text{stones}[l] }dp[i][j]=mini≤k<jdp

最低0.47元/天 解锁文章
637

被折叠的 条评论
为什么被折叠?



