luogu1880 石子合并

http://www.elijahqi.win/archives/495
题目描述
在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
输入输出格式
输入格式:

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

输出格式:

输出共2行,第1行为最小得分,第2行为最大得分.

输入输出样例
输入样例#1:
4
4 5 9 4
输出样例#1:
43
54

有合并过程可以看出,从第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}

本算法的时间效率为O(n3)。
f[i][j]表示第i个位置起,连续j个合并起来代价的最小或最大 比如f[i][1]就是f[i]本身

预处理一个sum出来,表示的含义和f相同

因为成环,所以我们把接到后面,然后就可以当作链来做了

注意:因为变成链处理,所以我们在预处理s数组的时候也要处理i>n的某些情况。

我就是这个错误到现在的qwq

#include<cstdio>
#define inf 0x7fffffff
#define N 110
int n,fmin[N<<1][N],fmax[N<<1][N],a[N<<1],s[N<<1][N];
inline int max(int a,int b){
    return a>b?a:b;
}
inline int min(int a,int b){
    return a<b?a:b;
}
int main(){
    freopen("1880.in","r",stdin);
//  freopen("1880.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;++i) scanf("%d",&a[i]);
    for (int i=n+1;i<=n<<1;++i) a[i]=a[i-n];
    for (int j=1;j<=n;++j)
        for (int i=1;i<=2*n-j;++i) if (j==1) s[i][1]=a[i];else s[i][j]=s[i][j-1]+a[i+j-1];
//  for (int i=1;i<=n;++i)
//      for (int j=1;j<=n;++j) fmin[i][j]=inf;
    for (int j=2;j<=n;++j){
        for (int i=1;i<=2*n-j;++i){
            fmax[i][j]=0;fmin[i][j]=inf;
            for (int z=i+1;z<i+j;++z){
                fmax[i][j]=max(fmax[i][j],s[i][j]+fmax[i][z-i]+fmax[z][j-(z-i)]);
                fmin[i][j]=min(fmin[i][j],s[i][j]+fmin[i][z-i]+fmin[z][j-(z-i)]);
            }
        //  printf("%d ",fmin[i][j]);
        }//printf("\n");
    }
    int ans1=inf,ans2=0;
    for (int i=1;i<=n;++i){
    //  printf("%d\n",fmin[i][n]);
        ans1=min(ans1,fmin[i][n]);
        ans2=max(ans2,fmax[i][n]);
    }
    printf("%d\n%d",ans1,ans2);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值