[NOI1995] 石子合并
题目描述
在一个圆形操场的四周摆放 \(N\) 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 \(2\) 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 \(N\) 堆石子合并成 \(1\) 堆的最小得分和最大得分。
输入格式
数据的第 \(1\) 行是正整数 \(N\),表示有 \(N\) 堆石子。
第 \(2\) 行有 \(N\) 个整数,第 \(i\) 个整数 \(a_i\) 表示第 \(i\) 堆石子的个数。
输出格式
输出共 \(2\) 行,第 \(1\) 行为最小得分,第 \(2\) 行为最大得分。
样例 #1
样例输入 #1
4
4 5 9 4
样例输出 #1
43
54
提示
\(1\leq N\leq 100\),\(0\leq a_i\leq 20\)。
解析
对于环的处理,显然复制一倍做成链,然后区间DP。\(f[i][j]和g[i][j]\)分别表示将\([i,j]\)区间的石子合并成一堆后的最大/最小得分。状态转移方程:\(f[i][j] = max(f[i][k] + f[k+1][j] + sum[i][j])\),sum是前缀和。g同理。
n的范围在100以内,\(O(n^3)\)可行。
代码
#include <bits/stdc++.h>
using namespace std;
int n, ansx, ansn, f[205][205], g[205][205], a[205], s[205];
int d(int i, int j) {return s[j] - s[i - 1];}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++) {
scanf("%d", &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 <= 2 * n; len ++) {
for (int i = 1, j; (j = i + len - 1) <= 2 * n; i ++) {
g[i][j] = 1e9;
for (int k = i; k < j; k ++) {
f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j] + d(i, j));
g[i][j] = min(g[i][j], g[i][k] + g[k + 1][j] + d(i, j));
}
}
}
ansn = 1e9;
for (int i = 1; i <= n; i ++) {
ansx = max(ansx, f[i][i + n - 1]);
ansn = min(ansn, g[i][i + n - 1]);
}
printf("%d\n%d", ansn, ansx);
return 0;
}
标签:得分,205,int,NOI1995,石子,合并,P1880,leq,DP
From: https://www.cnblogs.com/YHxo/p/16821456.html