题目描述:
将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
- 选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。
- 选择一种合并石子的方案,使得做 n−1次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。
第二行包含 n 个整数,分别表示每堆石子的数量。
输出格式
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
数据范围
1≤n≤200
输入样例:
4
4 5 9 4
输出样例:
43
54
分析:
在做本题前需要先熟悉AcWing 282 石子合并问题,本题的重点在于如何处理环形问题。在普通的石子合并问题中,我们可以通过枚举最后一次合并的位置k来求出石子合并的最小代价。状态表示:f[i][j]表示合并区间[i,j]上的石子所花费的最小代价,状态转移方程为f[i][j] = min(f[i][j],f[i][k]+f[k+1][j]+w[i][j]),其中w[i][j]表示合并区间[i][j]中石子质量之和。状态转移的过程可以通过第一重循环枚举区间长度len,第二重枚举区间起点l,第三重枚举最后一次合并的位置k来实现。问题在于如何将环形石子合并问题转化为普通的石子合并问题。
对于一堆环形的石子,我们采用某种顺序去合并,最后必然是将某两堆合并为一堆,而且环形的石子必然有两堆石子间存在缺口。
本题的合并顺序,环形石子最后出现的缺口位置也不同,设环形的序列是a1,a2,...,an,(an与a1也是相邻的)。则缺口可能出现在a1到a2,a2到a3,...,an到a1之间,我们可以用一重循环枚举缺口出现的位置,知道了缺口在哪,就相当于把环形的石子切开成一个线性排列的石子,就可以用普通的石子合并的解法去做了。但是本题n的范围是200,枚举缺口的一重循环加上普通石子合并问题的三重循环,时间复杂度是O(n^4),也就是8亿级别的运算,会超时。考虑优化下本题的解法,根据前面的分析可知,环形石子合并问题相当于求n次普通的石子合并,即分别求a1到an,a2,a3,...,an,a1,等等n个区间的石子合并问题,通常采取的办法就是将普通的石子序列后面再加一个石子序列,形成一个长度为2n的石子序列,即a1,a2,...,an,a1,a2,...,an,从前往后枚举长度为n的区间分别是a1到an,a2到a1,a3到a2等等,也就是环形石子合并问题所需要的区间,都出现在了这样一个长度为2n的区间中,我们对这样一个长度为2n的区间做下石子合并,最后遍历下所有长度为n区间的合并代价的最大值和最小值就是本题所求的答案了。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 405;
int a[N],s[N],f[N][N],g[N][N];
int main(){
int n;
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++) s[i] = s[i-1] + a[i];
for(int len = 2;len <= n;len++){
for(int l = 1;l + len - 1 <= 2*n;l++){
int r = l + len - 1;
f[l][r] = 1e9;
for(int k = l;k < r;k++){
f[l][r] = min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
g[l][r] = max(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);
}
}
}
int res1 = 1e9,res2 = 0;
for(int i = 1;i <= n;i++){
res1 = min(res1,f[i][i+n-1]);
res2 = max(res2,g[i][i+n-1]);
}
cout<<res1<<endl<<res2<<endl;
return 0;
}
注意本题既要求最小代价又要求最大代价,所以需要分别用两个dp数组f和g存储,另外注意边界情况的处理,求最小代价时初始情况所有长度为1区间内的石子f的值都是0,而长度大于1区间内石子f的值都是无穷大,而求最大代价的g数组则无需额外处理。(关于石子合并问题的四边形优化就继续往后拖再写啦!)