【算法实验四】--【动态规划】--石子合并

1148.石子合并

时限:1000ms 内存限制:10000K  总时限:3000ms

描述

在一个圆形操场的四周摆放着n堆石子(n<= 100),现要将石子有次序地合并成一堆。规定每次只能选取相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。编一程序,读入石子堆数n及每堆的石子数(<=20)。选择一种合并石子的方案,使得做n-1次合并,得分的总和最小; 比如有4堆石子:4 4 5 9 则最佳合并方案如下:
4 4 5 9 score: 0
8 5 9 score: 8
13 9 score: 8 + 13 = 21
22 score: 8 + 13 + 22 = 43

 

输入

可能有多组测试数据。 当输入n=0时结束! 第一行为石子堆数n(1<=n<=100); 第二行为n堆的石子每堆的石子数,每两个数之间用一个空格分隔。

 

输出

合并的最小得分,每个结果一行。

 

输入样例

4 4 4 5 9 0

 

输出样例

43

解析:这个题,,我原以为它是和矩阵连乘乘积的那个题一样,后来发现有两点不同。第一点是对于矩阵连乘的题目,num=arr[i][k]+arr[k+1][j]+row[i]*col[k]*col[j],这里一个新的arr的数是两个子项代价加起来,然后再加这两个子项算的代价,而这个摆石子是算的两个子项加起来,然后再加这两个的和。。。好吧我有点卡住了,我不知道怎么把以前的代价加进去。

像矩阵连乘那样,把对角线都设为0,算当前节点m [ i ] [ j ] 时,取其中间节点h , 然后算m[i[[j]=m[i][h]+m[h+1][j]+x,这个x为把这两个子节点连起来的代价,矩阵连乘是将 i , h , j 的维数乘起来,这里的话,应该是将当前两堆直接加起来,举个例子,如:

对于数据7,3,4,8,其产生的矩阵就应该为:

0       10      21      43            

0       0        7        22                   

0       0        0        12

0       0        0         0

上面为第一次算法后,第一次的m[i][j]就等于stone[i]+stone[j],当进行到m[1][3]和m[2][4]时,如m[1][3]=min( m[1][1]+m[2][3]+stone[1]+stone[2]+stone[3] ,m[1][2]+m[3][3]+stone[1]+stone[2]+stone[3]),m[2][4]=min( m[2][2]+m[3][4]+stone[2]+stone[3]+stone[4] ,m[2][3]+m[4][4]+stone[1]+stone[2]+stone[3] )。

事实证明,写博客有利于整理思路!啊哈哈哈,详见代码:

#include <iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int n;
int stone[100];
int m[100][100],s[100][100];//s是记录路径的
int num(int m,int n)
{
    int res=0;
    for(int i=m;i<=n;i++)
        res+=stone[i];
    return res;
}
int main()
{
    while(~scanf("%d",&n)&&n)
    {
        memset(stone,0,sizeof(stone));
        memset(m,0,sizeof(m));
        memset(s,0,sizeof(s));
        for(int i=1;i<=n;i++)
        {
            cin>>stone[i];
        }
        for(int r=1;r<n;r++)
        {
            for(int i=1;i<=n-r;i++)
            {
                m[i][i+r]=m[i][i]+m[i+1][i+r]+num(i,i+r);
                s[i][i+r]=i;
                int j;
                for(j=i+1;j<i+r;j++)
                {
                    if(m[i][j]+m[j+1][i+r]+num(i,i+r)<m[i][i+r])
                    {
                        m[i][i+r]=m[i][j]+m[j+1][i+r]+num(i,i+r);
                        s[i][i+r]=j;
                    }
                }
            }
        }
        cout<<m[1][n]<<endl;
        /*
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
                cout<<m[i][j]<<" ";
            cout<<endl;
        }
        */
    }
    return 0;
}

不过上面这个是仿照矩阵连乘写的,本题中石子是环形的,那么那个n维矩阵每一个元素都要用到了,循环里面也需加一点东西就可以。那么对于主层循环,

for(int r=1;r<n;r++)
        {
            for(int i=1;i<=n-r;i++)
            {
                m[i][i+r]=m[i][i]+m[i+1][i+r]+num(i,i+r);
                s[i][i+r]=i;
                for(int j=i+1;j<i+r;j++)
 间隔r自然是从1到n-1不变的,对于每一个开头的 i,应该改为 i<=n 即可,算法是m[i][(i+r)%n],既可以是m[1][3],也可以是m[3][1],且这两种算法是不同的,对应的 j 应该是1 2 3和3 4 1,用统一来表示就要取余,详见代码:

#include <iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int n;
int stone[100];
int m[100][100],s[100][100];//s是记录路径的
int num(int s,int t)
{
    int res=0;
    if(s<=t)
    {
        for(int i=s;i<=t;i++)
            res+=stone[i];
    }
    else
    {
        for(int i=s;i<n;i++)
            res+=stone[i];
        for(int i=0;i<=t;i++)
            res+=stone[i];
    }
    return res;
}
int main()
{
    while(~scanf("%d",&n)&&n)
    {
        memset(stone,0,sizeof(stone));
        memset(m,0,sizeof(m));
        memset(s,0,sizeof(s));
        for(int i=0;i<n;i++)
        {
            cin>>stone[i];
        }
        for(int r=1;r<n;r++)
        {
            for(int i=0;i<n;i++)
            {
                m[i][(i+r)%n]=m[i][i]+m[(i+1)%n][(i+r)%n]+num(i,(i+r)%n);
                s[i][(i+r)%n]=i;
                int k=r-1;
                for(int j=(i+1)%n;k>=0;j=(j+1)%n,k--)
                {
                    if(m[i][j%n]+m[(j+1)%n][(i+r)%n]+num(i,(i+r)%n)<m[i][(i+r)%n])
                    {
                        m[i][(i+r)%n]=m[i][j%n]+m[(j+1)%n][(i+r)%n]+num(i,(i+r)%n);
                        s[i][(i+r)%n]=j;
                    }
                }
            }
        }
        int x=1000;
        for(int i=0;i<n;i++)
        {
            if(m[i][(i+n-1)%n]<x)
                x=m[i][(i+n-1)%n];
        }
        cout<<x<<endl;
    }
    return 0;
}

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值