题目描述.
在一个圆形操场的四周摆放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来解决,又因为n≤100所以我们可以用二维数组f[i][j]来做DP
那么比较显然的是,i,j可以被我们用来表示一个区间的左右端点,f[i][j]表示区间合并成一堆石子时的最优解。
那么我们考虑怎么转移
假设我们已经求出了其他区间的最优解,那么合并i到j时,我们只需要枚举在哪个点合并,
设枚举的点为k,则转移方程为
f
[
i
]
[
j
]
=
m
a
x
/
m
i
n
(
f
[
i
]
[
k
]
+
f
[
k
+
1
]
[
j
]
+
∑
i
=
l
n
=
r
a
[
i
]
)
f[i][j] = max/min(f[i][k] + f[k + 1][j] + \sum_{i=l}^{n = r}a[i])
f[i][j]=max/min(f[i][k]+f[k+1][j]+i=l∑n=ra[i])
∑
\sum
∑那部分我们显然可以用前缀和优化一下
然后大小都做一遍就行了
#include<bits/stdc++.h>
#define MAXN 100010
#define N 200
#define ll long long
#define rg register
#define gtc() getchar()
#define INF 0x3f3f3f3f
using namespace std;
template <class T>
inline void read(T &s){
s = 0; T w = 1, ch = gtc();
while(!isdigit(ch)){if(ch == '-') w = - 1; ch = gtc();}
while(isdigit(ch)){s = s * 10 + ch - '0'; ch = gtc();}
s *= w;
}
int f1[N][N], f2[N][N];
int a[N], sum[N];
int n;
inline int s(int i, int j){return sum[j] - sum[i-1];}
int main()
{
read(n);
sum[0] = 0;
for(int i = 1; i <= n; ++i){
read(a[i]); a[i + n] = a[i];
}
for(int i = 1; i <= 2 * n; ++i) sum[i] = sum[i - 1] + a[i];
for(int u = 1; u < n; ++u){
for(int i = 1, j = i + u;(j < n + n) && (i < n + n); ++i, j = i + u){
f2[i][j] = INF;
for(int k = i; k < j; ++k){
f1[i][j] = max(f1[i][j], f1[i][k] + f1[k + 1][j] + s(i, j));
f2[i][j] = min(f2[i][j], f2[i][k] + f2[k + 1][j] + s(i, j));
}
}
}
int ans1 = 0, ans2 = INF;
for(int i = 1; i <= n; ++i){
ans1 = max(ans1, f1[i][i + n - 1]);
ans2 = min(ans2, f2[i][i + n - 1]);
}
printf("%d\n%d\n", ans2, ans1);
return 0;
}