目录
题目:
DP分析:
代码:
题目:
设有 N 堆石子排成一排,其编号为 1,2,3,…,N。
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 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。问题是:找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式
第一行一个数 N 表示石子的堆数 N。
第二行 N 个数,表示每堆石子的质量(均不超过 1000)。
输出格式
输出一个整数,表示最小代价。
数据范围
1≤N≤300
输入样例:
4 1 3 5 2
输出样例:
22
DP分析:
状态表示:使用
- 集合: 表示将 堆之间的石子合并到一起的所有方案
- 属性:所有方案合并代价的最小值
状态计算:
由于题目中只能合并相邻的石子,那么合并 堆的最后一步一定是将 中的左边一堆石子和右边的一堆石子合并。那么只需要确定 中的分界点,从而确定合并左边石子和右边石子的代价,那么只需要求出分别合并左边石子以及合并右边石子的代价最小值。
分界点可以从 枚举到 (分界点包含在左边石子中)。
因此:
- 状态表示为:
其中 表示的是合并左边石子和右边石子的代价(前缀和表示和求解)
代码:
import java.io.*;
import java.util.*;
class Main{
static int N = 310;
static int[] s = new int[N];
static int[][] f = new int[N][N];
public static void main(String[] args) throws IOException{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(in.readLine());
String[] str = in.readLine().split(" ");
for(int i=1;i<=n;i++) s[i] = s[i-1]+Integer.parseInt(str[i-1]); // 求前缀和,因为合并两个区间需要加上这个区间的全部质量
// 初始化
for(int i=1;i<=n;i++){
Arrays.fill(f[i],0x3f3f3f3f);
f[i][i] = 0;
}
// DP
/*
错的,比如求[1,3] ,需要区间[1,1],[1,2],[2,3],[3,3]在此之前,在[1,2]的时候求出了[1,2],但是没有[2,3]
所以我们应该先把所有的小区间求出来,逐次递增区间的大小
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
for(int k=i;k<j;k++){
f[i][j] = Math.min(f[i][k]+f[k+1][j]+s[j]-s[i-1],f[i][j]);
}
}
}
*/
for(int len=1;len<=n;len++){ // 区间长度=len+1
for(int i=1;i<=n;i++){ // 枚举左端点
int j = i+len; // 右端点
for(int k=i;k<j&&j<=n;k++){ // 分界点
f[i][j] = Math.min(f[i][k]+f[k+1][j]+s[j]-s[i-1],f[i][j]);
}
}
}
System.out.println(f[1][n]);
}
}
其中需要注意的是要先求出所有小区间的代价,在逐次递增求大区间的代价。
比如求 的最小代价 ,需要区间 在此之前,在 的时候求出了 的最小代价,但是没有 的最小代价。