蓝桥杯-合并石子 (经典动态规划)

题目大意:假设有一排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].


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值