区间 DP
简介 #
区间 DP 以区间为研究对象. 通常设 $f[l,r]$ 为区间 $[l,r]$ 的值,用 $f[小区间]$ 的值推出 $f[大区间]$ 的值.
问题 #
$n$ 堆石子排成一列,第 $i$ 堆石子重量为 $A_i$. 每次合并相邻两堆石子,消耗的体力值为其重量和. 求将所有石子合并为一堆,最少消耗多少体力.
原理 #
$f[l,r]$:合并第 $l$ 堆至第 $r$ 堆石子的最少体力值.
合并第 $l\sim r$ 堆石子可分为三步(设 $k$ 为 $[l, r)$ 中的某个数):
-
合并第 $l\sim k$ 堆石子,消耗体力值 $f[l,k]$.
-
合并第 $k+1\sim r$ 堆石子,消耗体力值为$f[k+1,r]$.
-
合并剩下两堆石子,消耗体力值 $\displaystyle\sum_{i=l}^r A_i$.
枚举 $k$,找出最小的体力值.
$$f[l,r]=\min_{l≤k< r}\{f[l,k]+f[k+1,r]\}+\sum_{i=l}^r A_i$$
其中 $\displaystyle\sum_{i=l}^r A_i$ 可以用 前缀和 优化.
计算顺序为 $f[小区间]\rightarrow f[大区间]$,故枚举区间长度的循环在最外层.
时间复杂度为 $O(n^3)$
memset(f, 0x7f, sizeof f);
for (int i = 1; i <= n; i ++) {
f[i][i] = 0;
sum[i] = sum[i - 1] + a[i];
}
for (int len = 2; len <= n; len ++) { // 区间长度
for (int l = 1; l + len - 1 <= n; l ++) { // 枚举区间左端点
int r = l + len - 1; // 区间右端点
for (int k = l; k < r; k ++)
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r]);
f[l][r] += sum[r] - sum[l - 1];
}
}