首页 > 其他 > 详细

[NOI1995]石子合并 题解

时间:2019-08-26 21:19:58      阅读:97      评论:0      收藏:0      [点我收藏+]

一道经典的dp题

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

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

我们先看下这道题的简单版本

有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

这道题不是环状的,我们可以直接dp解决,一开始我设的是f[i][j]表是合并i-j这个区间内的最小代价,于是有了状态转移方程

f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) (i<=k<=j)

于是写下了下面的代码

#include<bits/stdc++.h>
using namespace std;
int n,f[110][110],a[110];
int s[110];
int main(){
   scanf("%d",&n);
   memset(f,0x3f,sizeof(f));
   for(int i=1;i<=n;++i){
       scanf("%d",&a[i]);
       f[i][i]=0;
       s[i]=s[i-1]+a[i];
   }
   for(int i=1;i<=n;++i){
       for(int j=i;j<=n;++j){
           for(int k=i;k<=j;++k){
               f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
           }
       }
   }
   printf("%d",f[1][n]);
   return 0;
} 

然鹅答案错误,为什么?

在同机房的大佬的帮助下我明白了

因为求大区间是要用到小区间的值,可是上面这个程序固定了起点就一直向后跑会导致有些点更新过晚,要用的时候却用不到,于是我写下了下面的代码

#include<bits/stdc++.h>
using namespace std;
int n,f[110][110],a[110],T=10;
int s[110];
int main(){
    scanf("%d",&n);
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        f[i][i]=0;
        s[i]=s[i-1]+a[i];
    }
    while(T--){
        for(int i=1;i<=n;++i){
            for(int j=i;j<=n;++j){
                for(int k=i;k<=j;++k){
                    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
                }
            }
        }
    }
    printf("%d",f[1][n]);
    return 0;
} 

既然一遍跑不出答案,那我多跑几遍不就好了,同机房的大佬都震惊了,虽然答案是对的,但却并不是正解,正解应该是在外面枚举长度,再枚举起点,算出终点dp

代码

#include<bits/stdc++.h>
using namespace std;
int n,f[110][110],a[110],T=10;
int s[110];
int main(){
    scanf("%d",&n);
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        f[i][i]=0;
        s[i]=s[i-1]+a[i];
    }
    for(int L=2;L<=n;++L){
        for(int i=1;i<=n;++i){
            int j=i+L-1;
            for(int k=i;k<=j;++k){
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
            }
        }
    }
    printf("%d",f[1][n]);
    return 0;
} 

回到 noi1995这道题,我们看到环便可直接加倍断环成链(套路),其余思路同上面相似

#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,f[410][410],g[410][410],a[410];
int s[410],maxn,minn=1<<30;
int main(){
    scanf("%d",&n);
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        f[i][i]=0;
        s[i]=s[i-1]+a[i];
    }
    for(int i=1;i<=n;++i){
        s[i+n]=s[i+n-1]+a[i];
        f[i+n][i+n]=0;//这个初始化一定要记得
    }
    for(int L=2;L<=n;++L){
        for(int i=1;i<=n+n;++i){//起点可以枚举到2n
            int j=i+L-1;
            if(j>n*2) break;//不符合
            for(int k=i;k<j;++k){
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
                g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+s[j]-s[i-1]);
            }
        }
    }
    for(int i=1;i<=n;++i){
        maxn=max(maxn,g[i][i+n-1]);
        minn=min(minn,f[i][i+n-1]);
    }
    printf("%d\n%d\n",minn,maxn);
    return 0;
} 

[NOI1995]石子合并 题解

原文:https://www.cnblogs.com/donkey2603089141/p/11414990.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!