NUIST OJ 1410 石子合并 [DP]

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HZEUHJEJNJ/article/details/79983875

题目

题目描述

设有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] );
                }
            }
        }

未完待续

我现在不能理解的是,为什么不能从一排石子钟,每次找出相邻两个加和最小的一组数字进行合并。这是所谓的贪心吧?看不出来哪里错了哇。

猜你喜欢

转载自blog.csdn.net/HZEUHJEJNJ/article/details/79983875