石子合并(区间型动规)

在一个圆形操场的四周摆放n堆石子(n≤500),现要将石子有次序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

编一程序,由文件读入堆数n及每堆的石子数;

⑴ 选择一种合并石子的方案,使得做n-1次合并,得分的总和最小;

⑵ 选择一种合并石子的方案,使得做n-1次合并,得分的总和最大。

输入文件:

第1行为n,代表n堆石子;

第2行为n个数,代表每堆石子的数量。

输出文件:

第1行为合并的得分总和最小值;

第2行为合并的得分总和最大值。

输入样例:

4

4 5 9 4(从最上面的一堆数起,顺时针数)

输出样例:

43(最小)

54(最大)


【分析】

6堆石子合并的最小得分方案min{merge(①,②,③,④,⑤,⑥)}是由merge(①,②,③)+merge(④,⑤,⑥)得来的,上述中,merge表示合并。merge(①,②,③)时,它有两种合并方案,即先merge(①,②)两堆,再将①②合并的结果与第③堆合并,或先merge(②,③),再将②③合并的结果与第①堆合并。两种方案的合并过程如下:

第一种合并方案:(3+4)+6合并得分为7+13=20;

第二种合并方案:3+(4+6)合并得分为10+13=23。

两种方案相比,明显第一种方案得分小。merge(④,⑤,⑥)时,同样的,也有两种方案,即先merge④,⑤两堆,再将④⑤合并的结果与第⑥堆合并,或merge⑤,⑥,再将⑤⑥合并的结果与第④堆合并。两种方案的合并过程如下:

第一种合并方案:(5+4)+2合并得分为9+11=20;

第二种合并方案:5+(4+2)合并得分为6+11=17。

这两种方案相比,明显第二种方案得分小。从图2-2中的最小合并得分结果来看,在计算6堆石子合并的最小得分min{merge(①,②,③,④,⑤,⑥)}时,它的计算过程明显是取min{merge(①,②,③)}和min{merge(④,⑤,⑥)}的。由此可以初步看出,合并石子时具备最优子结构的性质。详细看合并1到6堆时存在的过程。

合并1堆:①,②,…,⑥;

合并2堆:①②,②③,…,⑥①;

合并3堆:①②③,②③④,…,⑥①②;

合并4堆:①②③④,②③④⑤,…,⑥①②③;

合并5堆:①②③④⑤,②③④⑤⑥,…,⑥①②③④;

合并6堆:①②③④⑤⑥,②③④⑤⑥①,…,⑥①②③④⑤。

有合并过程可以看出,从第i堆开始合并j堆时,它的值可以为第i堆+min{merge(i+1,i+2,…,i+j-1)}。如从第①堆开始合并4堆时,它可以为第①堆+min{merge(②,③,④)},可以为min{merge(①,②)}+min{merge(③,④)},可以为min{merge(①,②,③)}+第④堆,共有3种来源,与区间的合并有关。以此类推,合并到6堆时,取从第i堆开始合并6堆的最小值,即得到合并的总的最小值。所以,我们可以肯定地说,此问题具备最优子结构的性质,而且无后效性。

用合并的堆数作阶段,用f[i,j]作状态,表示从第i堆数起,顺时针合并j堆的总得分最小值,它包括合并前j-1堆的最小总得分加上这次合并的得分,用sum[i,j]表示这次合并的得分。合并时的堆数可以表示为序列{第i堆,第i+1堆,…,第(i+j-2) mod n+1堆}。序列总得来的方案有很多种,我们用子序列1和子序列2表示,如子序列1为{第i堆},则子序列2为{第i+1堆,…,第(i+j-2)mod n+1堆}。子序列1和子序列2相邻,所以,假如子序列1为k堆,则子序列2为j-k堆。由此,可以得到动规方程:

f[i,j]=min{f[i,k]+f[i+k,j-k]+sum[i,j],1≤k≤j-1}

用stone[i]表示初始时每堆的石子数,则动规的初始条件为:

f[i,1]=0

动规的边界为1≤i<n,1≤j≤n。求最大得分与最小得分方法一样,只是在计算时反一下即可。本算法的时间效率为O(n 3)

#include<cstdio>
#include<iostream>
#include<cstring>
#define INF 65535
using namespace std;
int n;
int num[510];///每堆石头数
int sum[510][510];
int fmin[510][510];///存储最小
int fmax[510][510];///存储最大
int main()
{

    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&num[i]);
        sum[i][1]=num[i];///预设从第i堆开始的合并1堆石头的得分
        fmin[i][1]=0;
        fmax[i][1]=0;
    }
    ///预设好从第i 堆开始合并j堆石头的当次得分
    for(int j=2;j<=n;j++)
    {
        for(int i=1;i<=n;i++)
        {
            sum[i][j]=num[i]+sum[i%n+1][j-1];///从i 开始合并 j堆的得分。就是num[i]加上从i+1开始合并j-1堆的得分,因为是环形区间,所以i要mod n;

        }
    }
    ///以合并的堆数为阶段,做动态规划
    for(int j=2;j<=n;j++)///阶段,即合并堆数
    {
        for(int i=1;i<=n;i++)///合并的开始位置
        {
            fmin[i][j]=INF;
            fmax[i][j]=0;
            int t=sum[i][j];
            for(int k=1;k<=j-1;k++)///设置前j-1堆石头合并的位置
            {
                int x=(i+k-1)%n+1;
                if(fmin[i][k]+fmin[x][j-k]+t<fmin[i][j])
                    fmin[i][j]=fmin[i][k]+fmin[x][j-k]+t;
                if(fmax[i][k]+fmax[x][j-k]+t>fmax[i][j])
                    fmax[i][j]=fmax[i][k]+fmax[x][j-k]+t;
            }
        }
    }
    ///依次判断石头的合并位置,求出最小,最大得分
    int maxn=0;
    int mint=INF;
    for(int j=1;j<=n;j++)
    {
        if(fmin[j][n]<mint)
            mint=fmin[j][n];
        if(fmax[j][n]>maxn)
            maxn=fmax[j][n];
    }
    printf("%d ",mint);
    printf("%d",maxn);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值