Problem
portal: P1880 石子合并
Description
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
Input
数据的第1行是正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
Output
输出共2行,第1行为最小得分,第2行为最大得分.
Sample
Sample Input
4
4 5 9 4
Sample Output
43
54
Solution
Analysis
设max_score(start, end)是起点为第start堆石子,终点为第end堆石子的最高得分。
对于这部分石子堆,存在第mid堆石子,使得最后一步的合并为start至mid这部分石子合并的一个石子堆与mid+1至end这部分石子合并的一个石子堆的合并,并且左半部分的得分是最高的,右半部分的得分也是最高的。
得到公式
max_score(start,end) = max(max_score(start, mid) + max_score(mid + 1) + total(start, end)) (start != end)
左半部分的最高得分+右半部分的最高得分+最终合并的得分 mid的取值为start一直到end的前一个 (可能存在5->6->1->2->3->4这种情况, 此时mid取5, 6, 1, 2, 3)
max_score(start,end) = 0 (start == end)
根据跨度逐渐增加,最后计算出这些石子堆的最高得分。
最低得分类似,不过每次求最小的。
细节见代码
Code
#include <bits/stdc++.h>
#define MAX_N 105
using namespace std;
int n, cnt[MAX_N];
// 最大得分计算所需要的中间值存储
int max_score[MAX_N][MAX_N];
// 最小得分计算所需要的中间值存储
int min_score[MAX_N][MAX_N];
// 统计从i到j堆石子的总数, i可能会小于j, 此时从i计算到n, 再从1计算到j
int total[MAX_N][MAX_N];
// 当前堆的下一个堆的序号
int next(int x) {
if (x == n) return 1;
else return x + 1;
}
int main(void) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> cnt[i];
}
// 两个for循环, 完善所有起点为第i堆, 终点为第j堆的石子的总数
for (int i = 1; i<= n; i++) {
total[i][i] = cnt[i];
}
for (int range = 1; range < n; range++) {
for (int i = 1; i <= n; i++) {
int j = i + range;
if (j > n) j -= n;
total[i][j] = total[next(i)][j] + cnt[i];
}
}
// 一个石子堆不用合并, 结果为0
for (int i = 1; i <= n; i++) {
max_score[i][i] = 0;
min_score[i][i] = 0;
}
int start, end;
int max_result = 0, min_result = INT_MAX;
// 跨度逐渐增加, 从起点与后面的第一个石子堆合并, 到起点与后面的n-1个, 即所有的石子堆合并
for (int range = 1; range < n; range++) {
int max_value, min_value;
// 由于是环形操场, 所以起点不是固定的
for (start = 1; start <= n; start++) {
end = start + range;
if (end > n) end -= n; // 环形操场
max_value = 0;
min_value = INT_MAX;
for (int mid = start; mid != end; mid = next(mid)) {
// 使用公式
max_value = max(max_value, max_score[start][mid] + max_score[next(mid)][end] + total[start][end]);
min_value = min(min_value, min_score[start][mid] + min_score[next(mid)][end] + total[start][end]);
}
// 存储起点到终点这部分的 最大/最小 值
max_score[start][end] = max_value;
min_score[start][end] = min_value;
// 等所有的石子堆都参与时, 找出起点不同时最大/最小得分的值
if (range == n - 1) {
max_result = max(max_result, max_score[start][end]);
min_result = min(min_result, min_score[start][end]);
}
}
}
cout << min_result << endl;
cout << max_result << endl;
}