题目描述
将 $n$ 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 $n$ 及每堆的石子数,并进行如下计算:
1. 选择一种合并石子的方案,使得做 $n-1$ 次合并得分总和最大。
2. 选择一种合并石子的方案,使得做 $n-1$ 次合并得分总和最小。
输入格式
输入第一行一个整数 $n$,表示有 $n$ 堆石子。
第二行 $n$ 个整数,表示每堆石子的数量。
输入格式
输出共两行:
第一个为合并得分总和最小值,
第二行为合并得分总和最大值。
样例输入
4
4 5 9 4
样例输出
43
54
数据范围及提示
对于 $100 \%$的数据,有 $1 \leq n \leq 200$
时空限制
时间限制:1s
空间限制:512MB
解题分析
区间 $DP$ 入门经典题,因为是一个环形的操作,所以我们将数组复制一遍再进行 $DP$,最后用每一个长度为 $n$ 的段统计答案
令数组 $f[l][r]$ 表示第 $l$ 到 $r$ 堆石子合并为一堆总分的最小值
令数组 $g[l][r]$ 表示第 $l$ 到 $r$ 堆石子合并为一堆总分的最大值
$$f[l][r] = min(f[l][k], f[k][r]) + \sum_{i=l}^{r} a_i $$
$$g[l][r] = max(g[l][k], g[k][r]) + \sum_{i=l}^{r} a_i $$
再用一个前缀和维护区间和即可
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 400 + 10;
const int INF = 0x3f3f3f3f;
inline int read(){
int x = 0;
char ch = getchar();
while(ch < '0' || ch > '9')
ch = getchar();
while('0' <= ch && ch <= '9'){
x = x*10 + ch - '0';
ch = getchar();
}
return x;
}
int data[MAXN];
int f[MAXN][MAXN];
int g[MAXN][MAXN];
int sum[MAXN];
int n;
int maxn = -INF;
int minn = INF;
int main(){
n = read();
for(register int i=1; i<=n; i++){
data[i] = data[n+i] = read();
}
for(register int i=1; i<=2*n; i++){
sum[i] = sum[i-1] + data[i];
}
for(register int len=2; len <= n; len++){
for(register int l = 1; l <= 2*n - len + 1; l++){
int r = l + len - 1;
f[l][r] = INF;
g[l][r] = -INF;
for(register int k=l; k<r; k++){
f[l][r] = min(f[l][r], f[l][k] + f[k+1][r]);
g[l][r] = max(g[l][r], g[l][k] + g[k+1][r]);
}
f[l][r] += sum[r] - sum[l-1];
g[l][r] += sum[r] - sum[l-1];
}
}
for(register int i=1; i<=n; i++){
maxn = max(maxn, g[i][i+n-1]);
minn = min(minn, f[i][i+n-1]);
}
printf("%d\n", minn);
printf("%d", maxn);
return 0;
}