问题描述
在一个圆形操场的四周摆放 n n n 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的 2 2 2 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。试求出计算出将 n n n 堆石子合并成 1 1 1 堆的最小得分和最大得分。
输入格式
第一行输入一个整数
n
n
n ,表示有
n
n
n 堆石子。
第二行输入
n
n
n 个整数,表示第
i
i
i 堆石子的个数
a
i
a_i
ai。
输出格式
输出一行两个整数,表示最小得分和最大得分。
样例
样例输入1:
3
1 2 3
样例输出1:
9 11
数据范围
1
≤
n
≤
100
1 \le n \le 100
1≤n≤100
1
≤
a
i
≤
1000
1 \le a_i \le 1000
1≤ai≤1000
题解
当看见合并这类的问题,可以想到 区间dp
和 贪心
。由于这道题并不能局部最优推出全局最优,因此使用 区间dp
。
定义 d p 1 i , j dp1_{i, j} dp1i,j 表示从 i i i 到 j j j 最小得分, d p 2 i , j dp2_{i, j} dp2i,j 表示从 i i i 到 j j j 最大得分。
最开始每堆石子都没有合并,因此 d p 1 i , i = d p 2 i , i = 0 dp1_{i, i} = dp2_{i, i} = 0 dp1i,i=dp2i,i=0。
转移方程以
d
p
1
dp1
dp1 为例。
将
i
i
i 到
j
j
j 合并为一堆石子,可以把
i
i
i 到
k
k
k 和
k
+
1
k + 1
k+1 到
j
j
j 合并起来(
i
≤
k
<
j
i \le k < j
i≤k<j),即
d
p
1
i
,
k
+
d
p
1
k
+
1
,
j
dp1_{i, k} + dp1_{k + 1, j}
dp1i,k+dp1k+1,j,最后加上
i
i
i 到
j
j
j 的和即可。
d
p
1
i
,
j
=
m
i
n
(
d
p
1
i
,
j
,
d
p
1
i
,
k
+
d
p
1
k
+
1
,
j
+
s
u
m
j
−
s
u
m
i
−
1
)
dp1_{i, j} = min(dp1_{i, j}, dp1_{i, k} + dp1_{k + 1, j} + sum_{j} - sum_{i - 1})
dp1i,j=min(dp1i,j,dp1i,k+dp1k+1,j+sumj−sumi−1)
同理, d p 2 i , j = m a x ( d p 2 i , j , d p 2 i , k + d p 2 k + 1 , j + s u m j − s u m i − 1 ) dp2_{i, j} = max(dp2_{i, j}, dp2_{i, k} + dp2_{k + 1, j} + sum_{j} - sum_{i - 1}) dp2i,j=max(dp2i,j,dp2i,k+dp2k+1,j+sumj−sumi−1)。
最后输出 d p 1 1 , n dp1_{1, n} dp11,n 和 d p 2 1 , n dp2_{1, n} dp21,n 即可。
但是由于是圆形,因此需要破环为链。
可将原数组再复制一遍,进行计算。
int s[210];//前缀和
int f1[210][210], f2[210][210];//dp1 和 dp2
int main(){
输入
//破环为链(复制)
for(int i = 1; i < n; ++ i){
a[i + n] = a[i];
}
//前缀和
//因为 1 到 n 相当于 n + 1 到 2 * n,因此循环到 2 * n - 1即可
for(int i = 1; i < 2 * n; ++ i){
s[i] = s[i - 1] + a[i];
}
for(int i = 1; i < n; ++ i){
for(int j = 1; j <= 2 * n - i; ++ j){
int t = i + j;
f1[j][t] = 2147483647;
//合并 j 到 t 的区间
for(int k = j; k < t; ++ k){
f1[j][t] = min(f1[j][k] + f1[k + 1][t], f1[j][t]);
}
f1[j][t] += s[t] - s[j - 1];
}
}
int ans1 = 2147483647;
for(int i = 1; i < n; ++ i){
ans1 = min(ans1, f1[i][i + n - 1]);
//从 i 到 i + n - 1,相当于从 i 开始合并的数组
}
for(int i = 1; i < n; ++ i){
for(int j = 1; j <= 2 * n - i; ++ j){
int t = i + j;
f2[j][t] = -2147483647;
//合并 j 到 t 的区间
for(int k = j; k < t; ++ k){
f2[j][t] = max(f2[j][k] + f2[k + 1][t], f2[j][t]);
}
f2[j][t] += s[t] - s[j - 1];
}
}
int ans2 = -2147483647;
for(int i = 1; i < n; ++ i){
ans2 = max(ans2, f2[i][i + n - 1]);
//从 i 到 i + n - 1,相当于从 i 开始合并的数组
}
输出
}