问题很经典,
在一个圆形操场的四周摆放着
n
n
堆石子。现要将石子有次序地合并成一堆。规定每次只
能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将堆石子合并成一堆的最小得分和最大得分。
这道题是经典的环形DP,看到网络上一堆
dalao
d
a
l
a
o
都是使用了
f(i,j)
f
(
i
,
j
)
表示从i开始,顺时针合并j堆所得的最小的得分(最大得分一会儿说)。我就写一个用
f(i,j)
f
(
i
,
j
)
表示从i合并到j所得的最小的得分。状态转移方程也很好想:
f(i,j)={0,f(i,k)+f(k+1,j)+sum[j]−sum[i−1],i==ji<>j
f
(
i
,
j
)
=
{
0
,
i
==
j
f
(
i
,
k
)
+
f
(
k
+
1
,
j
)
+
s
u
m
[
j
]
−
s
u
m
[
i
−
1
]
,
i
<>
j
求解最大得分比求解f更为简单,
具体看程序。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
const int MAXN = 500;
int a[MAXN], f[MAXN][MAXN], g[MAXN][MAXN];
int main() {
int n;
scanf("%d", &n);
memset(f, 63, sizeof f);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
a[i] += a[i - 1];
f[i][i] = f[n + i][n + i] = 0;
}
for (int i = n + 1; i <= 2 * n; i++)
a[i] = a[n] + a[i - n];
int min = 0x3f3f3f3f, max = -0x3f3f3f3f;
for (int i = 2 * n; i >= 1; i--)
for (int j = i + 1; j <= i + n + 1; j++)
for (int k = i; k < j; k++) {
f[i][j] = std::min(f[i][j], f[i][k] + f[k + 1][j] + a[j] - a[i - 1]);
g[i][j] = std::max(g[i][j], g[i][k] + g[k + 1][j] + a[j] - a[i - 1]);
}
for (int i = 1; i <= n; i++) {
min = std::min(min, f[i][i + n - 1]);
max = std::max(max, g[i][i + n - 1]);
}
std::cout << min << std::endl << max << std::endl;
return 0;
}