题目大意:假设有一排n堆石子,每堆石子有若干个小石子,要求将它们合并成一堆,需要花费的最小代价。而且每次合并只能将相邻的两堆合并,合并的代价是两堆石子的重量之和。
题目分析:因为不能合并有间隔的石子堆,所以这不是一道哈夫曼树的例子(哈夫曼树:利用贪心算法,每次合并重量最小的两堆石子)。
通过分解子问题,我们可以发现,当只有一堆石子时,合并代价为0;
当有两堆石子时,合并代价是两堆石子重量之和;
当有三堆石子时,合并代价是前两个合并,再和第三个合并;或者后两个先合并,再和第一个合并。这两种方法取一个最小值;
那么,由此得知:我们可以令dp[i][j]表示合并第i堆石子,到第j堆石子花费的最小值。
dp[i][j] = min(dp[i][k],dp[k+1][j]) + sum[i][j] , 其中sum[i][j]表示第i堆石子到第j堆石子之间所有石子的重量和
上式解释:要合并第i堆石子-第j堆石子,我们可以分为三步,第一步合并第i堆石子-第k堆石子,第二步合并第k+1堆石子-第j堆石子,第三步,将合并的两堆石子再合并,代价是它们之间所有的石子重量和。其中,k可以从i取到j-1.
当i=j时,也就是只有一堆石子,dp[i][j] 自然就是0.
至此,我们可以画出以下的状态转移表,自底向上求解,将前一步的解保存在一个表中:
i|j |
0 |
1 |
2 |
3 |
4 |
0 |
0 |
3 |
11 |
? |
? |
1 |
- |
0 |
5 |
? |
? |
2 |
- |
- |
0 |
7 |
? |
3 |
- |
- |
- |
0 |
9 |
4 |
- |
- |
- |
- |
0 |
上表是例子:1 2 3 4 的一个状态表。 其中dp[i][j]中,j必须大于i,所有左下部分为空。dp[i][j] = 0 (i==j) 所以对角线上元素为0 然后我们计算的是 dp[0][1],dp[1][2],dp[2][3],dp[3][4], 然后是基于前面的结果计算dp[0][2],dp[1][3],dp[2][4]……
所以计算顺序是按照斜对角线的方向,慢慢往上计算。我们可以发现,每一斜列的j-i都是一个定值,比如dp[0][0],dp[1][1],dp[2][2],dp[3][3]的j-i=0. dp[0][1],dp[1][2],dp[2][3],dp[3][4]的j-i=1. dp[0][2],dp[1][3],dp[2][4]的j-i=2……
我们可以按照j-i的值作为循环的变量,记作l. (l=0, l=1, l=2, l=3……)
代码展示:
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
int main(){
int n;
cin>>n;
int num[n];
long long sum[n];
memset(sum,0,sizeof(sum));
for(int i=0;i<n;i++){
cin>>num[i];
if(i==0)
sum[i] = num[i];
else
sum[i] = sum[i-1] + num[i];
}
long long dp[n][n];
memset(dp,0,sizeof(dp));
long long temp = 0;
for(int l=1;l<n;l++){ //每一条斜线代表的i与j的差值
for(int i=0;i<n&&i+l<n;i++){
long long min_value = dp[i][i]+dp[i+1][i+l];
for(int k=i+1;k<=i+l-1;k++){
temp = dp[i][k]+dp[k+1][i+l];
if(temp<min_value)
min_value = temp;
}
if(i>0)
dp[i][i+l] = min_value + (sum[i+l] - sum[i-1]);
else
dp[i][i+l] = min_value + sum[l];
}
}
cout<<dp[0][n-1]<<endl;
return 0;
}
为了使最后一组数据不超时,我们可以用sum[]一维数组代替二维数组,即用sum[i]存储前i个元素之和,这样第i个元素到第j个元素的和可以表示为sum[j] - sum[i-1].