题目
题目描述
设有N堆沙子排成一排,其编号为1,2,3,…,N(N<=100)。每堆沙子有一定的数量。现要将N堆沙子并成为一堆。归并的过程只能每次将相邻的两堆沙子堆成一堆(每次合并花费的代价为当前两堆沙子的总数量),这样经过N-1次归并后成为一堆,归并的总代价为每次合并花费的代价和。找出一种合理的归并方法,使总的代价最小。
例如:有3堆沙子,数量分别为13,7,8,有两种合并方案,
第一种方案:先合并1,2号堆,合并后的新堆沙子数量为20,本次合并代价为20,再拿新堆与第3堆沙子合并,合并后的沙子数量为28,本次合并代价为28,将3堆沙子合并到一起的总代价为第一次合并代价20加上第二次合并代价28,即48;
第二种方案:先合并2,3号堆,合并后的新堆沙子数量为15,本次合并代价为15,再拿新堆与第1堆沙子合并,合并后的沙子数量为28,本次合并代价为28,将3堆沙子合并到一起的总代价为第一次合并代价15加上第二次合并代价28,即43;
采用第二种方案可取得最小总代价,值为43。
输入描述
输入由若干行组成,第一行有一个整数,n(1≤n≤100);表示沙子堆数。第2至n+1行是每堆沙子的数量。
输出描述
一个整数,归并的最小代价。
样例输入
7
13
7
8
16
21
4
18
样例输出
239
题目分析
首先这个题目需要注意的是:相邻的堆才可以合并,一开始我没有看到这个条件,因此每次选两个最小的合并,这显然是错误的。
思来想去,这道题只能用区间型DP来做了
本题的区间DP
用dp[i][j]表示合并[i,j]区间所需要的最小代价
那么可以方便的得到如下关系
若i=j,则dp[i][j]=0
否则,则dp[i][j]=min{dp[i][k]+dp[k+1][j]}+sum[i][j]
发现:计算dp[i][j]的大区间,需要先计算出dp[i][j]的小区间,所以可以考虑设定一个变量len,从小往大递增,根据此来控制先算小区间,再算大区间
最后,本题要求的是合并所有石子,即[i,j]区间和并的最小代价。
因此,本题答案储存在dp[1][n]内
整体代码与运行结果
/*
ZhangBinjie@Penguin
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define maxn 105
using namespace std;
int a[maxn];
int dp[maxn][maxn];
int sum[maxn][maxn];
int main() {
int n;
while (cin >> n) {
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n; ++i) {
sum[i][i] = a[i];
for (int j = i+1; j <= n; ++j) {
sum[i][j] = sum[i][j - 1] + a[j];
}
}
for (int len = 2; len <= n; ++len) {
for (int i = 1; i <= n - len + 1; ++i) {
dp[i][i + len - 1] = dp[i][i] + dp[i + 1][i + len - 1] + sum[i][i + len - 1];
for (int k = i + 1; k < i + len - 1; ++k) {
dp[i][i + len - 1] = min(dp[i][i + len - 1], dp[i][k] + dp[k + 1][i + len - 1] + sum[i][i + len - 1]);
}
}
}
cout << dp[1][n] << endl;
}
return 0;
}
结果如下
另一种写法
我这里是用了len来控先算小的区间再算大的区间
这个博文却让j从后往前推巧妙的避免了这个问题。
for(int i=1; i<=n; ++i){
for(int j=i-1; j>=1; --j){
for(int k=j; k<i; ++k){
dp[j][i] = min(dp[j][i], dp[j][k] + dp[k+1][i] + sum[j][i] );
}
}
}
未完待续
我现在不能理解的是,为什么不能从一排石子钟,每次找出相邻两个加和最小的一组数字进行合并。这是所谓的贪心吧?看不出来哪里错了哇。