cogs 80. 石子归并 动态规划

80. 石子归并

★★   输入文件:shizi.in   输出文件:shizi.out   简单对比
时间限制:1 s   内存限制:128 MB

设有N堆沙(shi)子排成一排,其编号为1,2,3,…,N(N<=100)。每堆沙子有一定的数量。现要将N堆沙子并成为一堆。归并的过程只能每次将相邻的两堆沙子堆成一堆(每次合并花费的代价为当前两堆沙子的总数量),这样经过N-1次归并后成为一堆,归并的总代价为每次合并花费的代价和。找出一种合理的归并方法,使总的代价最小。

例如:有3堆沙子,数量分别为13,7,8,有两种合并方案,
第一种方案:先合并1,2号堆,合并后的新堆沙子数量为20,本次合并代价为20,再拿新堆与第3堆沙子合并,合并后的沙子数量为28,本次合并代价为28,将3堆沙子合并到一起的总代价为第一次合并代价20加上第二次合并代价28,即48;
第二种方案:先合并2,3号堆,合并后的新堆沙子数量为15,本次合并代价为15,再拿新堆与第1堆沙子合并,合并后的沙子数量为28,本次合并代价为28,将3堆沙子合并到一起的总代价为第一次合并代价15加上第二次合并代价28,即43;

采用第二种方案可取得最小总代价,值为43。

【输入格式】

输入由若干行组成,第一行有一个整数,n(1≤n≤100);表示沙子堆数。第2至n+1行是每堆沙子的数量。 

【输出格式】

一个整数,归并的最小代价。

【输入样例】

输入文件名:shizi.in

7
13
7
8
16
21
4
18

【输出样例】

输出文件名:shizi.out

239

 

 

唉 已经多年没有做dp了

我本来dp就一窍不通(暴力走天下)

现在觉得还是练一下为好(暴力并非通用)

 

这一道题看起来好像还是可以暴力的哦(n<=100)

但是我们用动态规划来弄一下试试

 

这一道题很显然 是一道动态规划题

我们可以用f[i][j]表示从第i个石子到第j个石子的最小的合并费用

我们当然要再弄一个前缀和的数组 sum[i]表示前i个石子的重量和

 

状态转移方程竟然想出来了!

f[i][j]=min(f[i][k]+f[k+1][j]+sum[i]-sum[j-1])

那么这个样子就非常的简单了

也就是说i<=k<j

就是说i-k  和  k+1-j  这两石子合并起来 首先要加上原来这两堆石子已经产生的代价 就是两个f   还有这两对石子合并在一起的代价

 

其实只要状态转移方程想出来了 就都非常简单了

代码也是非常的简短

 

 

 

但是 我却非常完美地想错了

这一道题我直接连样例都没有过 这是为什么呢?

首先先讲一讲 我原来是怎么做错的吧

我原来就是三层循环 ijk  若无其事地跑了一遍 发现结果输出了313

QAQ

这到底是是怎么回事呢?

终于想明白了

这一道题在石子进行合并的时候是先从比较短的长度逐渐合并成大区间的

所以三层循环 第一层应该是区间的长度才对 第二层是i  j就是i加上那个区间长度

最后一层是k

 

然后重点!这一道题的数据范围有坑儿 开105大小70分  115大小80分  最后无奈一下子开了一个205才100的

代码:

 

#include<bits/stdc++.h>
#define maxn 205
#define ll long long
using namespace std;
int n;
ll dp[maxn][maxn];
ll sum[maxn];
inline int read()
{
    int X=0; bool flag=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
    while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
    if(flag) return X;
    return ~(X-1);
}
int main()
{
    freopen("shizi.in","r",stdin);
    freopen("shizi.out","w",stdout);
    n=read();
    memset(dp,0x3f,sizeof(dp));
    for(int i=1,x;i<=n;i++) 
    {
        x=read(); sum[i]=sum[i-1]+x; dp[i][i]=0;
    }    
//    for(int i=1;i<n;i++)
//        for(int j=i+1;j<=n;j++)
//            for(int k=i;k<j;k++)
//                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
    for(int len=1;len<n;len++)
        for(int Left=1;Left<n;Left++)
        {
            int Right=Left+len;
            for(int Middle=Left;Middle<Right;Middle++)
                dp[Left][Right]=min(dp[Left][Right],dp[Left][Middle]+dp[Middle+1][Right]+sum[Right]-sum[Left-1]);
        }
    printf("%lld",dp[1][n]);
    return 0;
}

 

转载于:https://www.cnblogs.com/Tidoblogs/p/11420302.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值