题目描述
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
输入输出格式
输入格式:
数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出格式:
输出共2行,第1行为最小得分,第2行为最大得分.
输入输出样例
输入样例#1: 复制
4 4 5 9 4
输出样例#1: 复制
43 54
这道题属于动态规划中的“区间DP”,顾名思义,区间DP是以区间长度作为阶段的,在本题中,所谓的“区间长度”其实可以形象地理解成一段区间合并的花费。 让我们先从最简单的情况入手。 假设现在这里有一条链(先不考虑环),有四个数,分别是1,2,3,4。 它们合并de情况有很多种,从最终状态考虑。分别是【(1),(2,3,4)】【(1,2),(3,4)】【(1,2,3),(4)】这三种; 毫无疑问,肯定是最后一种情况最省花费,研究它会发现,我们应该在(1,2,3)中再寻找最省花费的合并方法,而这些方法跟后边的合并显然没有关系,这也就符合无后效性原则。 接下来我们再把它抽象一点,假如我们现在得到了一条链。并且我们只需要去做最后的合并。 把这两部分合并起来,显然是左边的已有花费加上右边已有花费再加上他们的和(每次合并的花费) 以此类推到更多堆石子的情况。对于begin到end之间的石子,去枚举“界限”
状态转移方程:(f[i][j]表示i堆到j堆最小花费,g[i][j]表示从i堆到j堆最大花费)
1 2 |
|
于是董永建老人家为我们提供了一个更妙的思想。把这个环储存成一个更大的链。每次去枚举开头,自然就有了结尾。而且每种断开的方法都是这条链的字串(不是子序列!!!)。
最后打擂台取最大值即可。
#include<iostream>
#include<vector>
#include<climits>
#include<queue>
#include<string>
#include<algorithm>
using namespace std;
int a[205];
int s[205];
int res[205][205];
int res1[205][205];
auto d = [](int a, int b) {return s[b] - s[a-1];};
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 p = 1; p < n; p++)
{
for (int i = 1,j = i + p;(j <2*n) && (i < 2*n); i++,j = i + p)//间隔为1到间隔为n-1的所有的都计算一次
{
res1[i][j] = 1000001;
for (int k = i; k <j; k++)//相等的时候代表只有一个没有意义,不需要分割
{
res[i][j] = max(res[i][j], res[i][k] + res[k+1][j] + d(i, j));
res1[i][j] = min(res1[i][j], res1[i][k] + res1[k + 1][j] + d(i, j));
}
}
}
int MAX = 0;
int MIN = 1001000;
for (int i = 1; i <= n; i++)
{
MAX = max(res[i][i + n - 1], MAX);
MIN = min(res1[i][i + n - 1], MIN);
}
cout << MIN <<endl<< MAX << endl;
return 0;
}