来源:P1880 [NOI1995] 石子合并 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目描述
在一个圆形操场的四周摆放 NN 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 NN 堆石子合并成 11 堆的最小得分和最大得分。
输入格式
数据的第 11 行是正整数 NN,表示有 NN 堆石子。
第 22 行有 NN 个整数,第 ii 个整数 a_iai 表示第 ii 堆石子的个数。
输出格式
输出共 22 行,第 11 行为最小得分,第 22 行为最大得分。
输入输出样例
输入 #1复制
4 4 5 9 4输出 #1复制
43 54说明/提示
1≤N≤100,200≤ai≤20。
红色标出的为重要理解点 。
圆形操场排放且取相邻的石子堆表示这是一个环啊,如果没有注意到这个问题可能会错得猝不及防。
本题思路:区间dp + 链转环的处理 + 前缀和
关于链转环关键就是:a1+n赋值为a1,a2+n赋值为a2,a3+n赋值为a3,······
后续计算将 n 变为 2*n
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200;
int a[N];
int sum[N];
int dpmin[N][N];
int dpmax[N][N];
int n;
int main()
{
cin>>n;
memset(dpmin,0x7f7f7f7f,sizeof(dpmin)); // 求最小值的数组初始值要设得足够大
for(int i=1;i<=n;i++) // 先将输入数据存入数组
{
cin>>a[i];
a[i+n]=a[i]; // 将a[i+n]赋值为a[i]
}
for(int i=1;i<=n*2;i++)
{
dpmin[i][i]=0;
dpmin[i][i]=0;
}
sum[0]=0;
for(int i=1;i<=n*2;i++) // 前缀和
{
sum[i]=sum[i-1]+a[i];
}
for(int len=2;len<=n;len++) // 区间dp
{
for(int i=1;i+len-1<=2*n;i++)
{
int j=i+len-1;
for(int k=i;k<j;k++) // k一定小于j
{
dpmin[i][j]=min(dpmin[i][j],dpmin[i][k]+dpmin[k+1][j]+sum[j]-sum[i-1]);
dpmax[i][j]=max(dpmax[i][j],dpmax[i][k]+dpmax[k+1][j]+sum[j]-sum[i-1]);
}
}
}
int res1=0x7f7f7f7f,res2=0; // 最后要遍历出区间大小为n的合并最大值和最小值
for(int i=1;i<=2*n;i++)
{
res1=min(res1,dpmin[i][i+n-1]);
res2=max(res2,dpmax[i][i+n-1]);
}
cout<<res1<<endl<<res2;
return 0;
}
嗯满意满意^o^