携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情
[NOI1995] 石子合并
题目描述
在一个圆形操场的四周摆放 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 堆石子合并成 堆的最小得分和最大得分。
输入格式
数据的第 行是正整数 ,表示有 堆石子。
第 行有 个整数,第 个整数 表示第 堆石子的个数。
输出格式
输出共 行,第 行为最小得分,第 行为最大得分。
样例 #1
样例输入 #1
4
4 5 9 4
样例输出 #1
43
54
提示
,。
一、贪心
贪心只能处理“任取两堆”,而不能处理“相邻两堆”。任取两堆的题目就是 合并果子。
二、划分阶段
本题是区间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;
}