区间动态规划 (Interval Dynamic Programming)
区间 DP 是一类以区间长度作为阶段演进指标的动态规划。其核心逻辑是由短区间的解逐步归纳出长区间的解,通常用于处理具有“合并”或“拆分”物理特性的问题。
1. 形式化建模:导出与验证
1.1 状态转移方程的导出 (Derivation)
对于闭区间 ,其最优解通常源于其子区间 与 的某种最优合并方案。
基本归纳式:
- 边界条件:。
- 计算序 (Ordering):由于 依赖于长度更短的 和 ,必须按区间长度 从小到大进行递推。
1.2 最优子结构的形式化证明:以矩阵链乘为例
命题:设矩阵序列 的最优乘法顺序在 处断开,则其子序列 的乘法顺序也必须是该子序列的最优顺序。
证明 (反证法): 若存在另一顺序使得计算 的代价更小,由于总代价 ,我们将更小的 代入,将得到一个比 更小的总代价,这与 是最优代价矛盾。证毕。
1.3 收敛性分析 (Convergence Analysis)
基于长度的归纳逻辑: 区间 DP 的收敛性建立在区间长度 的单调递增上。
- 初始状态: 的解已给出。
- 状态依赖: 的解仅依赖于 的子区间解。
- 有限性:。 因此,外层循环控制 保证了算法必然在 步内收敛。
2. 核心技术:四边形不等式优化 (Knuth Optimization)
对于满足特定性质(如区间包含单调性和四边形不等式)的 函数,区间 DP 的复杂度可以从 优化至 。
2.1 四边形不等式与决策单调性证明
命题:若 满足四边形不等式且对于区间包含具有单调性,则最优分割点 满足决策单调性。
验证要点 (Verification): 在具体题目中,验证决策单调性的步骤如下:
- 代数验证:检查 是否成立。
- 打表观察:对于难以分析的 ,通过小程序输出小规模数据的 ,检查是否满足 。
复杂度飞跃
利用该性质,在枚举分割点 时,只需在 范围内查找。总代价为 。这是一个典型的伸缩求和 (Telescoping Sum),最终收敛于 。
3. 教材化典型例题
例题 1:石子合并 (四边形不等式优化版)
证明:合并代价 满足四边形不等式(事实上其满足 )。因此可以使用 Knuth 优化。
Check Solution (C++ )
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 1005;
int a[MAXN], s[MAXN];
int f[MAXN][MAXN], pos[MAXN][MAXN];
int main() {
int n;
while (cin >> n) {
for (int i = 1; i <= n; i++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
pos[i][i] = i;
}
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; i++) f[i][i] = 0;
for (int len = 2; len <= n; len++) {
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
// 核心:限制枚举范围 [pos[i][j-1], pos[i+1][j]]
for (int k = pos[i][j - 1]; k <= pos[i + 1][j]; k++) {
int val = f[i][k] + f[k + 1][j] + s[j] - s[i - 1];
if (val < f[i][j]) {
f[i][j] = val;
pos[i][j] = k;
}
}
}
}
cout << f[1][n] << endl;
}
return 0;
}
例题 2:括号匹配 (Bracket Sequence)
给定一个由 (、)、[、] 组成的字符串,求最少添加多少个字符使其变成合法括号序列。
Check Solution (C++)
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
bool match(char l, char r) {
return (l == '(' && r == ')') || (l == '[' && r == ']');
}
int main() {
string s; cin >> s;
int n = s.size();
if (n == 0) { cout << 0 << endl; return 0; }
vector<vector<int>> f(n, vector<int>(n, 1e9));
for (int i = 0; i < n; i++) f[i][i] = 1;
for (int len = 2; len <= n; len++) {
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
if (match(s[i], s[j])) f[i][j] = (len == 2 ? 0 : f[i + 1][j - 1]);
for (int k = i; k < j; k++)
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j]);
}
}
cout << f[0][n - 1] << endl;
return 0;
}
4. 课后强化练习
练习 1:能量项链 (环形区间 DP)
颗珠子组成环,第 颗珠子头标记 ,尾标记 。两珠子合并释放能量 。求最大能量。
Check Solution Hint
使用倍长数组处理环形结构。 表示合并从 到 段珠子的最大能量。 。
练习 2:多边形游戏 (Polygon)
注意处理乘法的最大值可能由两个极小负数相乘得到。
Check Analysis
维护 和 。对于乘法运算符:
5. 复杂度与边界总结
| 优化技术 | 适用场景 | 复杂度 | 核心判定 |
|---|---|---|---|
| 标准区间 DP | 任意合并成本 | 从小到大遍历 | |
| 四边形不等式 | 成本函数满足特定性质 | 决策点单调性 | |
| 倍长数组 | 环形结构 | 断环为链,长度限制为 |