前言
区间dp也符合动态规划的三阶段,但是也有自己特殊的地方,先来看一下总结,然后再在后面的题目中体验。
第一阶段定义dp数组
(1)缩小规模。在区间dp里,规模是区间的大小。当区间规模大小固定时,而不同的左右端点所产生的区间又是不一样的,所以需要两个维度来表示区间的左右端点。
(2)考虑限制。
(3)写出dp数组。
这里的转移要注意,一般背包DP,线性DP,对于dp[i]是从dp[i-1]转移的。
而区间DP,对于
d
p
[
l
]
[
r
]
dp[l][r]
dp[l][r]大多数是从
d
p
[
l
+
1
]
[
r
]
dp[l+1][r]
dp[l+1][r]和
d
p
[
l
]
[
r
−
1
]
dp[l][r-1]
dp[l][r−1]转移,考虑的时候可以优先考虑这样转移。
(4)修改dp数组。
第二阶段推导状态转移方程
第三阶段写代码
第一层for循环遍历规模,在区间dp里面就是遍历区间长度
第二层for循环遍历区间,遍历的是区间的左端点,根据区间长度和左端点,区间右端点就出来了。
第三层for循环遍历转移点,对于一个区间[l,r]来说,它可以通过[l,k]和[k+1,r]合成得到,第三层遍历的就是区间断点k。
石子合并
有 N堆石子排成一排,其编号为 1,2,3,…,N。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 44 堆石子分别为 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。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式
第一行一个数 N 表示石子的堆数 N。
第二行 N个数,表示每堆石子的质量(均不超过 1000)。
输出格式
输出一个整数,表示最小代价。
数据范围
1≤N≤300
输入样例
4
1 3 5 2
输出样例
22
题目分析
动态规划三阶段
第一个阶段定义dp数组
(1)缩小规模。我需要考虑的石子堆数为n,每次合并石子石子的堆数都会减少,那么我的规模就是当前合并的石子堆数。如果我要合并两堆石子,既可以是[2,3]也可以是[3,4],也就是这两堆石子的左右端点我要确定一下,那么定义 d p [ l ] [ r ] dp[l][r] dp[l][r]表示合并区间[l,r]里的石子为一堆。
(2)考虑限制。没有限制。
(3)定义dp数组。 d p [ l ] [ r ] dp[l][r] dp[l][r]表示合并区间[l,r]里的石子为一堆所花费的最小代价。
第二个阶段推导状态转移方程
对于区间合并,我们需要枚举区间的断点k,合并式子如下
d p [ l ] [ r ] = d p [ l ] [ k ] + d p [ k + 1 ] [ r ] + w [ l ] [ r ] + w [ k ] [ r ] dp[l][r]=dp[l][k]+dp[k+1][r]+w[l][r]+w[k][r] dp[l][r]=dp[l][k]+dp[k+1][r]+w[l][r]+w[k][r]
这里的 w [ l ] [ k ] w[l][k] w[l][k]和 w [ k ] [ r ] w[k][r] w[k][r]分别表示区间[l,k]石子的总质量和区间[k,r]石子的总质量。他要怎么求呢?希望能用O(1)的时间复杂度求,那么其实就是一个区间和,可以利用前缀和数组求解。区间[l,k]石子的总质量和区间[k,r]石子的总质量就是区间[l,r]石子的总重量。
d p [ l ] [ r ] = d p [ l ] [ k ] + d p [ k + 1 ] [ r ] + s u m [ r ] − s u m [ l − 1 ] dp[l][r]=dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1] dp[l][r]=dp[l][k]+dp[k+1][r]+sum[r]−sum[l−1]
第三个阶段写代码
(1)dp数组的初始化。最初的状态初始值需要初始化,最初的状态是一堆石子都没合并,也就是 d p [ i ] [ i ] = 0 dp[i][i]=0 dp[i][i]=0。其余的均初始化为最大值。
(2)递推dp数组。
a.第一层for循环表示的是区间长度 len表示当前合并的石子堆数 2:n
b.第二层for循环表示的是区间左端点 i表示区间左端点 1:n
区间右端点j=i+len-1
c.第三层for循环表示的是dp数组的转移点 k表示的是转移点 l:r-1
(3)表示答案。 d p [ 1 ] [ n ] dp[1][n] dp[1][n]
题目代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] a = new int[n+1];
long[] sum = new long[n+1];
long[][] dp = new long[n+1][n+1];
for (int i = 0; i < dp.length; i++) {
Arrays.fill(dp[i], Integer.MAX_VALUE);
}
for (int i = 1; i < n + 1; i++) {
a[i] = scanner.nextInt();
sum[i] = sum[i-1] + a[i];//预处理出来前缀和数组
dp[i][i] = 0;//dp数组初始化
}
for (int len = 2; len <= n; len++) {
for (int i = 1; i+len-1 < n+1; i++) {
int j = i + len-1;
for (int k = i; k < j; k++) {
dp[i][j] = Math.min(dp[i][j], dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
}
}
}
System.out.println(dp[1][n]);
}
}