P1880 [NOI1995]石子合并

题目描述

在一个圆形操场的四周摆放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

设dp[ i ] [ j ]为区间 [ i , j ]内 j - i + 1个 小堆堆成一个大堆的总分的最小/最大值。
则 dp [ i ] [ j ] =两子区间:dp[i][k]、dp[k+1][j]得分之和加上 两个子区间石子的总和,可以用前缀和求的。

#include<bits/stdc++.h>
using namespace std;

char buf[100000],*L=buf,*R=buf;
#define gc() L==R&&(R=(L=buf)+fread(buf,1,100000,stdin),L==R)?EOF:*L++;
template<typename T>
inline void read(T&x) {
    char ch=gc(); x=0;
    while (ch<'0'||ch>'9')ch=gc();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=gc();
}

int a[220],sum[220],n,dp_min[220][220],dp_max[220][220];
int main() {
    memset(dp_min,0x3f3f3f3f,sizeof(dp_min));
    memset(dp_max,-0x3f3f3f3f,sizeof(dp_max));
//    freopen("in.txt","r",stdin);
    read(n);
    for(int i=1; i<=n; ++i) {
        read(a[i]);
        a[n+i]=a[i];//化环为线
    }
    for(int i=1; i<=2*n; ++i) {
        sum[i]=sum[i-1]+a[i];
        dp_min[i][i]=0;//区间内只有一个石子,不能合成
        dp_max[i][i]=0;
    }
    for(int l=2; l<=n; ++l) {// l<=>j-i+1表示长度,长度小可以推出长度大的,故最外层是l
        for(int i=1; i<=2*n+-l; ++i) {
            int j=i+l-1;
            for(int k=i; k<j; ++k) {
                dp_min[i][j]=min(dp_min[i][j],dp_min[i][k]+dp_min[k+1][j]);//子区间总分的和的最小值
                dp_max[i][j]=max(dp_max[i][j],dp_max[i][k]+dp_max[k+1][j]);
            }
            dp_min[i][j]+=sum[j]-sum[i-1];//再加上该次合成的得分,即为区间[i,j]的最终得分。
            dp_max[i][j]+=sum[j]-sum[i-1];
        }
    }
    int ans_min=0x3f3f3f3f,ans_max=-0x3f3f3f3f;
    for(int i=1; i<=n; ++i) {
        ans_min=min(ans_min,dp_min[i][n+i-1]);//遍历长度为n的区间,最小值即为答案。
        ans_max=max(ans_max,dp_max[i][n+i-1]);
    }
    cout<<ans_min<<endl<<ans_max;
    return 0;
}
posted @ 2020-12-16 16:55  肆之月  阅读(56)  评论(0编辑  收藏  举报