题目描述
在一个圆形操场的四周摆放着n堆石子,现要将石子有次序地合并成一堆。规定每次只能选取相邻的两堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。编写程序,读入石子堆数n及每堆的石子数(<=20)。选择一种合并石子的方案,使得做n-1次合并,得分的总和最大(小)。
输入样例
4
4 4 5 9
输出样例
43
54
思路
类似于直线上环形石子合并问题,将原数组复制一份放到数组的尾部,可以巧妙地实现了每个石子作为起点的情况。枚举i-j的之间的分裂点k,则花费为f[i][k] + f[k+1][j] + s[j]-s[i-1]。
状态转移方程
f[i][j] = 0 ; i==j
f[i][j] = min(f[i][j],f[i][k] + f[k+1][j] + s[j]-s[i-1]) i<j
f[i][j]:从第i堆石子到第j堆石子合并起来的最小花费,k为分裂点
为计算方便,维护了一个s[]前缀和数组。
代码
#include<iostream>
using namespace std;
const int N = 1e3 + 10;
int a[N];
int f[N*2][N*2];
int g[N*2][N*2];
int sum[N*2];
int n;
int minans = 0x3f3f3f3f;
int maxans = 1 - 0x3f3f3f3f;
int main()
{
cin>>n;
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];
for(int len = 2; len <= n; len++)
{
for(int i = 1; i + len -1 <= 2 * n; i++)
{
int j = len + i -1;
f[i][j] = 0x3f3f3f3f;
g[i][j] = 1 - 0x3f3f3f3f;
for(int k = i; k < j; k++)
{
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + sum[j] - sum[i - 1]);
g[i][j] = max(g[i][j], g[i][k] + g[k + 1][j] + sum[j] - sum[i - 1]);
}
}
}
for(int i = 1; i <= n; i++)
{
minans = min(minans,f[i][i + n - 1]);
maxans = max(maxans,g[i][i + n - 1]);
}
cout<<minans<<endl;
cout<<maxans<<endl;
return 0;
}