在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数。求把所有石子合并成一堆的最小花费。
输入格式
输入第一行包含一个整数n,表示石子的堆数。
接下来一行,包含n个整数,按顺序给出每堆石子的大小 。
输出格式
输出一个整数,表示合并的最小花费。
样例输入
5
1 2 3 4 5
样例输出
33
数据规模和约定
1<=n<=1000, 每堆石子至少1颗,最多10000颗。
假如有5堆石头:7 6 5 7 30;
最后合成一堆的石头,肯定是由两堆石头合起来的;a,b两坨;a这坨可能是由原来的1堆,2堆,3堆,4堆合起来的;b坨对应的就是4,3,2,1坨。。
我们需要把这些情况都算出来:并取这四中情况最小的作为选择;
1+4;2+3;3+2;4+1;因为是相邻才能合并;所以1+4和4+1并不是一种情况;可以把1+4理解为a是第一堆,b是第二堆到第五堆。。
怎么算呢?假如是a=1;b=4这种情况;a=1,就是第一坨石头数量嘛:7;b坨又可以分为两坨c,d:1+3;2+2;3+1;
这些情况也需要算出来。。并选出这其中花费最小的组合;
怎么算。。假如c,d是1+3组合;这里c=1;自然是第二堆石头:6;d坨又可能分为e,f:1+2;2+1;算出这两种情况,并选出花费最小的组合;
怎么算!假如e,f为1+2的组合;e为1,即第三堆:5;f为2,自然为1+1;分别为第四堆和第五堆数量了;
用dp[i][j]记录从第i堆合并到第j堆的最小花费
所以需要从底层往上走;先算出两个两个一堆的dp[0][1],dp[1][2],dp[2][3],dp[3][4];
这样才能算三个三个一堆的:dp[0][2]=Min{dp[0][0]+dp[1][2] , dp[0][1],dp[2][2]}; 依次dp[1][3],dp[2][4];
算四个四个同一堆的:dp[0][3]=Min{dp[0][0]+dp[1][3] , dp[0][1]+dp[2][3] , dp[0][2] + dp[3][3]};依次dp[1][4];
最后算5个一堆的!dp[0][4] = Min{dp[0][0]+dp[1][4] , dp[0][1]+dp[2][4] , dp[0][2]+dp[3][4] , dp[0][3]+dp[4][4]};
你看最后的dp[0][4] 是不是就是 1+4;2+3;3+2;4+1;这四种组合选出最小的意思~
状态转移方程:
dp[i][j] = Min{dp[i][k] + dp[k+1][j]} + sum[i][j];(i!=j)(k从i到j-1);
其中sum[i][j]为i到j的石头总量;为啥还有这个?你看状态里不有两堆石头嘛,dp[i][k] ,dp[k+1][j] 这两堆石头合并还要花费啊。
import java.util.Scanner;
public class 提高算法合并石子DP {
static int sum;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner input = new Scanner(System.in);
int n = input.nextInt();
int stone[] = new int[n];
int dp[][] = new int[n][n];//dp[i][j]储存从i到j的石头合并需要的最少花费
for (int i = 0; i < n; i++) {
if (i==0) {
stone[i] = input.nextInt();
}else {
stone[i] = stone[i-1] + input.nextInt();
}
}
for (int i = 0; i < n; i++) {
dp[i][i] = 0;//从i到i的石头合并花费为0
}
for (int l = 2; l <= n; l++) {//遍历每种长度l从2->n
for (int start = 0; start < n-l+1; start++) {//遍历l长度下的每个起点
int end = start + l - 1;//对于此时的start对应的终点为end
dp[start][end] = Integer.MAX_VALUE;
for (int k = start; k < end; k++) {//对于dp[start][end]按k分割,有K中分割方案;
if (dp[start][end]>dp[start][k]+dp[k+1][end]) {
dp[start][end]=dp[start][k]+dp[k+1][end];//依次比较,取最小的方案;
}
}
//上面部分是分割了两部分;到这了dp[start][end]等于分割到两部分的花费,还需要加上把这两部分合并的花费
//也就是从start 到end 的石头数量
int sum = 0;
if (start==0) {
sum = stone[end];
}else {
sum = stone[end] - stone[start-1];
}
dp[start][end] += sum;//加上从start到end的石头数量
}
}
System.out.println(dp[0][n-1]);
}
}