题目描述
将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。
规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:
选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。
第二行包含 n 个整数,分别表示每堆石子的数量。
输出格式
输出共两行:
第一行为合并得分总和最小值,
第二行为合并得分总和最大值。
数据范围
1≤n≤200
输入样例:
4
4 5 9 4
输出样例:
43
54
思路: 求一个环形的石子合并的最小值,和最大值, 两部分可以一起做, 我们发现, 将n环形堆石子合并,最终合并成一堆石子, 我们合并了n - 1次 , 而n堆环形石子中有n段空隙, 即肯定存在两个石子之间的空隙没有被合并, 我们可以枚举这个断点, 但是时间复杂度就会变成 n 3 n^3 n3, 会TLE, 那我们直接考虑将俩段完全相同的石子接到一起, 然后直接求出长度为n的环形石子的最值,时间复杂度降到了 ( n + 1 ) 2 (n+1)^2 (n+1)2 。
代码:
#include <iostream>//学习DP让我懂得了不少知识呢, 差不多明白了为什么算法好的同学为什么DP都厉害了。>
#include <cstring>
using namespace std;
const int N = 410, INF = 0x3f3f3f3f;
int w[N], s[N], f[N][N], g[N][N];
int main()
{
int n; cin >> n;
for(int i = 1; i <= n; i ++)
{
cin >> w[i];
w[i + n] = w[i];
}
for(int i = 1; i <= 2 * n; i ++) s[i] = s[i - 1] + w[i];
memset(f, 0x3f, sizeof f);
memset(g, 0, sizeof g);
for(int len = 1; len <= n; len ++)
{
for(int l = 1; l + len - 1 <= 2 * n; l ++)
{
int r = l + len - 1;
if(len == 1) g[l][r] = 0, f[l][r] = 0;
else
{
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 maxv = 0, minv = INF;
for(int i = 1; i <= n; i ++)
{
maxv = max(g[i][i + n - 1], maxv);
minv = min(f[i][i + n - 1], minv);
}
cout << minv << '\n' << maxv << endl;
return 0;
}