计蒜课中沙子的质量(动态规划)感想

设有N堆沙子排成一排,其编号为1,2,3,…,N(N< =300)。每堆沙子有一定的数量,可以用一个整数来描述,现在要将这N堆沙子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆沙子的数量之和,合并后与这两堆沙子相邻的沙子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同,如有4堆沙子分别为  1    3    5    2  我们可以先合并1、2堆,代价为4,得到4  5  2  又合并  1,2堆,代价为9,得到9  2  ,再合并得到11,总代价为4+9+11=24,如果第二步是先合并2,3堆,则代价为7,得到4  7,最后一次合并代价为11,总代价为4+7+11=22;

问题是:找出一种合理的方法,使总的代价最小。输出最小代价。

输入格式:

第一行一个数N表示沙子的堆数N。 第二行N个数,表示每堆沙子的质量。  < =1000

输出格式:

合并的最小代价

样例输入

4
1 3 5 2

样例输出

22

这道题目用到了算法中的动态规划,而在此我要将自己对这道题目的感想写到这里。

动态规划问题在我看来,用简单的话去说就是将每一步的值记录下来然后在处理大问题的时候直接将小问题的结果拿来用就可以。

而递归问题就是便利所有情况,然后从所有情况中找出最优的解。

这里我放出来源码:

#include<cstdio>
#include<iostream>
using namespace std;
int min(int x,int y)
{
    if(x<y) return x;
    else return y;
}
int n,a,s[1000+10],f[1000+10][1000+10];
int main()
{
    s[0]=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a;
        s[i]=s[i-1]+a;
        f[i][i]=0;
    }
    for(int r=2;r<=n;r++)
    for(int i=1;i+r-1<=n;i++)
    {
        int k=i+r-1,t=s[i+r-1]-s[i-1];
        f[i][k]=f[i][i]+f[i+1][k]+t;
        for(int j=i+1;j<=k;j++) f[i][k]=min(f[i][k],f[i][j]+f[j+1][k]+t);
    }
    cout<<f[1][n]<<endl;
    return 0;
}

在这个问题中,我们应该如何利用动态规划的思想呢?

int MatrixChain(int n,int ss[]){

    
    for(int i=1;i<=n;i++)m[i][i]=0;
    for(int r=2;r<=n;r++)
        for(int i=1;i<=n-r+1;i++){
            int t = ss[i+r-1]-ss[i-1];
            int j = i+r-1;
            m[i][j] = m[i][i]+m[i+1][j]+t;
            b[i][j] = i; 
            for(int k=i+1;k<j;k++){
                int w = m[i][k]+m[k+1][j]+t;
                if(m[i][j]<w){
                    m[i][j] = w;b[i][j] = k;
                }
            }
        }
        for(int n =0;n<30;n++){
        
            for(int h =0;h<30;h++)
                cout<<b[n][h];
                cout<<endl;
                }
        return m[1][n];
} 

在这段代码中,我们创建m[i][j]数组来记录每一步的数值,在主函数中“ss[i+1] = ss[i] + p[i]”语句是记录前n个数的和。例如:输入1 3 5 7。ss[1] = 1   ss[2]=1+3  ss[3] = 1+3+5......

(为何要记录这个呢?这个我们下面讲)

之后使用for(int i=1;i<=n;i++)m[i][i]=0; 将二维数组的主对角线上的值都设置为0(在m矩阵中,横纵坐标ij分别表示第i个数到j个数,而m内存的值表示i到j的最优情况下的最优解)

for(int r=2;r<=n;r++)
for(int i=1;i<=n-r+1;i++)这里,我们使用两重循环,按照主对线的方向从左到右逐层计算m数组,m[i][j] = m[i][i]+m[i+1][j]+t;这时候我们要看题,题目要让 我们对各个数相加,所以我们将大问题[i][j]分解为[i+1][j]与[i][i]。因为我们分解问题后还要将这几个数在加一下,所以这时候t就有了它的作用。例如(1,2,3,4    我们要求1—3的值,这时候,m[i][i]=1  m[i+1][j] = 2+3   而t=1+2+3 这里这个t就是前面我们提到的ss[3]-ss[0](t = ss[i+r-1]-ss[i-1]))这个时候我们就从小问题开始逐层填充这个二维数组,后面的问题要用到前面的数据。

在最后,因为我们第一次得到的s[i][j]并不一定是最优解,所以这个时候我们要使用

for(int k=i+1;k<j;k++){
int w = m[i][k]+m[k+1][j]+t;
if(m[i][j]<w){
m[i][j] = w;b[i][j] = k;
}
}

这个函数中我们截取i到j中任意一个数k,求所有情况中最优的解,然后覆盖原来的数组。将截断的点放在b[i][j]中。

到程序结束后,我们会得到两个二维数组,一个里面放着i到j的最优解的值,一个放着最优解的断点位置。

这个算法就是这样,第一次写算法的感想。。。。以后还是多努力吧,毕竟算法渣。。。。

Come on!                                                                               -----------------------Creat By Pinging


原文地址:https://www.cnblogs.com/Pinging/p/7684638.html