【动态规划】石子合并

272 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

[NOI1995] 石子合并

题目描述

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

试设计出一个算法,计算出将 NN 堆石子合并成 11 堆的最小得分和最大得分。

输入格式

数据的第 11 行是正整数 NN,表示有 NN 堆石子。

22 行有 NN 个整数,第 ii 个整数 aia_i 表示第 ii 堆石子的个数。

输出格式

输出共 22 行,第 11 行为最小得分,第 22 行为最大得分。

样例 #1

样例输入 #1

4
4 5 9 4

样例输出 #1

43
54

提示

1N1001\leq N\leq 1000ai200\leq a_i\leq 20

一、贪心

贪心只能处理“任取两堆”,而不能处理“相邻两堆”。任取两堆的题目就是 合并果子。

二、划分阶段

本题是区间DP的模板题(然而对蒟蒻还是很难)

很容易想到用f[i][j]表示i~j的最大(小)得分。

三、状态转移

在i~j这一区间内,枚举下标k进行拆分。拆分区间是区间DP的重要思想之一。

合并ik与k+1j的区间,会加上i~j之间所有石子个数之和。

故我们得到状态转移方程为f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) (最小值max改为min)

其中s为前缀和。

#include<iostream>
#include<iomanip>
#include<cstring>
using namespace std;
int n,a[205],mi[205][205],ma[205][205],sum[205],ansmax,ansmin,r;
int main()
{
    memset(mi,0x3f,sizeof(mi));
    memset(ma,0,sizeof(ma));
    //初始化
    cin>>n;
    sum[0]=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i+n]=a[i];
    //输入
    }
    for(int i=1;i<2*n;i++)
    {
        sum[i]=sum[i-1]+a[i];
        mi[i][i]=ma[i][i]=0;
        //维护前缀和,再次初始化数组
    }
    for(int i=2;i<=n;i++)
    {
        for(int j=1;i+j-1<2*n;j++)
        {
            r=i+j-1;
            for(int k=j;k<r;k++)
            {
                ma[j][r]=max(ma[j][r],ma[j][k]+ma[k+1][r]+sum[r]-sum[j-1]);
                mi[j][r]=min(mi[j][r],mi[j][k]+mi[k+1][r]+sum[r]-sum[j-1]);//状态转移
            }
        }
    }
    ansmin=0x7fffffff;
    ansmax=-1;
    for(int i=1;i<=n;i++)
    {
        ansmin=min(ansmin,mi[i][i+n-1]);
        ansmax=max(ansmax,ma[i][i+n-1]);
    //选择最大(小)值
    }
    cout<<ansmin<<"\n"<<ansmax;
    return 0;
}