题目链接: 点我跳转
题目大意: 一个环形的数列,每次合并相邻的两个数,两数之和为得分,求合并所有数的最小得分和最大得分。
题目分析: 很经典的题了,我们可以将环形切开,在链上考虑合并情况。
(以下有关区间的讨论全部在左闭右开的情况下讨论)
我们首先来考虑最小得分的情况。对于每一条链上的合并情况,我们可以将链分成两条较短的链进行考虑。也就是
[
s
,
t
)
[s,t)
[s,t)可以分成
[
s
,
m
)
[s,m)
[s,m)和
[
m
,
t
)
[m,t)
[m,t)其中
s
<
m
<
t
s<m<t
s<m<t,通过枚举
m
m
m 就可以考虑到所有情况了。我们可以设
d
p
[
s
,
t
)
dp[s,t)
dp[s,t)为将
[
s
,
t
)
[s,t)
[s,t)所有石子合并的得分,那么状态转移方程就是
d
p
[
s
,
t
)
=
m
i
n
(
d
p
[
s
,
t
)
,
d
p
[
s
,
m
)
+
d
p
[
m
,
t
)
+
s
u
m
[
s
,
t
)
)
;
dp[s,t) = min(dp[s,t),dp[s,m)+dp[m,t)+sum[s,t));
dp[s,t)=min(dp[s,t),dp[s,m)+dp[m,t)+sum[s,t));这个式子不难理解:
[
s
,
t
)
[s,t)
[s,t)的得分是
[
s
,
m
)
[s,m)
[s,m)和
[
m
,
t
)
[m,t)
[m,t)的得分之和,再加上
[
s
,
t
)
[s,t)
[s,t)的石子总数量。
最大得分与最小得分基本一样,只需将
m
i
n
min
min 改为
m
a
x
max
max 即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int dp_mi[110][110],dp_ma[110][110],data[110],sum[210];
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>data[i];
for(int i=0;i<n;i++) // 初始化
for(int j=0;j<n;j++)
dp_mi[i][j] = 1e9;
for(int i=0;i<n;i++)dp_mi[i][(i+1)%n] = 0;
for(int i=0;i<=2*n;i++)sum[i+1] = data[i%n]; //这两行的目的是方便计算[s,t)区间和
for(int i=1;i<=2*n;i++)sum[i] += sum[i-1]; //使得 [s,t)区间和 = sum[t] - sum[s]
for(int l=2;l<=n;l++) //l是区间长度
for(int s=0;s<n;s++) //区间开始的位置
for(int i=s+1;i<s+l;i++){ //区间中点的位置
int m = i%n,t = (s+l)%n; //为了方便表示
dp_mi[s][t] = min(dp_mi[s][t],dp_mi[s][m]+dp_mi[m][t]+sum[s+l]-sum[s]);
dp_ma[s][t] = max(dp_ma[s][t],dp_ma[s][m]+dp_ma[m][t]+sum[s+l]-sum[s]);
}
int ans = 0x7fffffff;
for(int i=0;i<n;i++)ans = min(ans,dp_mi[i][i]);
cout<<ans<<endl;
for(int i=0;i<n;i++)ans = max(ans,dp_ma[i][i]);
cout<<ans;
return 0;
}