区间 DP 也属于线性 DP 中的一种,它以“区间长度”作为 DP 的阶段,使用两个坐标(区间的左、右端点)描述每个维度。

在区间 DP 中,一个状态由若干个比它更小的且包含于它的区间所代表的状态转移而来,其初态一般由长度为1的“元区间”构成。

沙子合并

问题

设有 $N$ 堆沙子排成一排,其编号从 1 到 $N$($N\leq 300$)。每堆沙子有一定的数量,可以用一个整数 $A_i$ 来描述,现在要将这 $N$ 堆沙子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆沙子的数量之和,合并后与这两堆沙子相邻的沙子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同,如有4堆沙子分别为 1、3、 5、2,我们可以先合并 1、2 堆,代价为 4,得到 4、5、2 又合并 1、2 堆,代价为 9,得到 9、2 ,再合并得到11,总代价为 4+9+11=24,如果第二步是先合并 2、3 堆,则代价为 7,得到 4、7,最后一次合并代价为11,总代价为 4+7+11=22;问题是:找出一种合理的方法,使总的代价最小。输出最小代价。

来源:石子合并

设 $F[l, r]$ 表示把最初的第 $l$ 堆到第 $r$ 堆(闭区间)沙子合并称一堆,需要的代价。则转移方程如下:
$$
F[l, r] = \min \limits_{l \leq k<r}\{ F[l,k] + F[k+1, r]\} + \sum_{i=l}^{r}A_i
$$
初始值: $\forall l\in[1,n], F[l,l] = 0$,目标:$F[1, N]$

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <algorithm>
#include <cstdio>

using namespace std;
const int N = 1006;

#define INF (0x3f3f3f3f)
int a[N];
int s[N];
int f[N][N];
int n;

int sum(int i, int j) {
return s[j] - s[i-1];
}

int main(int argc, const char * argv[]) {
// freopen("input.txt", "r", stdin);
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
s[0] = 0;
for (int i = 1; i <= n; ++i) {
s[i] = s[i-1]+a[i];
f[i][i] = 0;
}

for (int len = 2; len <= n; ++len) {
for (int i = 1; i<=n-len+1; ++i) {
int j = i+len-1;
f[i][j] = INF;
for (int k = i; k<=j-1; ++k) { // k >= i , k<=j-1
f[i][j] = min(f[i][j], f[i][k]+f[k+1][j]);
}
f[i][j] += sum(i, j);
}
}
printf("%d\n", f[1][n]);

return 0;
}