石子合并问题(动态规划)

【问题描述】

在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数。求把所有石子合并成一堆的最小花费。

【输入】

输入第一行包含一个整数n,表示石子的堆数。接下来一行,包含n个整数,按顺序给出每堆石子的数量大小 。

【输出】

输出一个整数,表示合并的最小花费。

【样例输入】

5

1 2 3 4 5

【样例输出】

33

【样例说明】

输入:一共有5堆石子,数量分别为1,2,3,4,5.

输出:最小花费为33.

给出【问题分析】、【递归公式及解释】,以及【根据样例得到的最优值的解释】。

问题分析:

这是一道动态规划中的区间DP问题。

引入dp、p、s数组

dp数组:最优化矩阵 dp[i][j]:表示第i堆到第j堆石子合并的最优值

p数组:最优化位置矩阵 p[i][j]:表示第i堆到第j堆石子合并时,最优位置是p[i][j]

s数组:记录石头的数量:s[i][j]表示第i堆到第j堆石子的总数量

如果只有一堆石子,不需要合并,费用为0。

如果有两堆石子,需要一次合并,费用为两堆石子数量之和。

如果有三堆石子,需要两次合并,费用为第1堆和第2堆合并以及第2堆和第3堆合并费用中的较小值,加上三堆石子数量。

如果有n堆石子,需要n-1次合并,可用k进行分隔,分为第1到第k堆和第k+1到第n堆,(k从1取到n-1),两部分分别求合并费用,k取使两部分的合并费用总和最小的那个值,最后再加上所有石子数量总和,即为整体的合并费用。

公式及解释:

dp[i][j]=0 i=j

dp[i][j] = min( dp[i][j], dp[i][k] + dp[k+1][j] + s[i][j] ) i≠j

dp[i][j]的最优解一定在i到j中间的两个最优解的和,再加上本次费用。

根据样例得到的最优解的解释:

33

最小花费33

dp矩阵如下:

0 3 9 19 33

0 0 5 14 28

0 0 0 7 19

0 0 0 0 9

0 0 0 0 0

再结合p矩阵

0 1 2 3 3

0 0 2 3 3

0 0 0 3 4

0 0 0 0 4

0 0 0 0 0

合并步骤为:

1和2合并得3,当前花费为3;

3和3合并得6,当前花费为6,总花费为9;

4和5合并得9,当前花费为9,总花费为18;

6和9合并得15,当前花费为15,总花费为33。

代码实现:

#include <bits/stdc++.h>
using namespace std;
int n, a[100];
int dp[100][100]; //dp[i][j]表示从第i堆到第j堆合并的费用
int s[100][100]; //石头的数量
int p[100][100]={0};
int merge (int n,int a[]){
    for (int i = 1; i <= n; i++)
    {
        s[i][i] = a[i];
        dp[i][i] = 0;
    }  
     //区间长度
        for (int len = 2; len <= n; len++)
        { //左端点 右端点最大不能超过n
            for (int i = 1; i + len - 1 <= n; i++)
            {        //右端点                
                int j = i + len - 1;
                //先将代价设为最大
                dp[i][j] = 1000000;
                for (int k = i; k <= j; k++) //用k来分割区间
                {
                    s[i][j] = s[i][k] + s[k + 1][j];
                    if(dp[i][j]>dp[i][k] + dp[k + 1][j] + s[i][j])
                    {
                        dp[i][j]=dp[i][k] + dp[k + 1][j] + s[i][j];
                        p[i][j]=k;
                    }
                }
            }
        }
        cout << dp[1][n] << endl;
}

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
            cin >> a[i];
    merge(n,a);
    
    //输出最优化矩阵dp       
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cout<<dp[i][j]<<' ';
        }    
    cout<<endl;
    }    
    
    //输出最优位置矩阵p
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cout<<p[i][j]<<' ';
        }    
    cout<<endl;
    }
    
    return 0;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值