传送门:http://lx.lanqiao.cn/detail.page?submitid=3262677
问题描述
在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数。求把所有石子合并成一堆的最小花费。
输入格式
输入第一行包含一个整数n,表示石子的堆数。
接下来一行,包含n个整数,按顺序给出每堆石子的大小 。
输出格式
输出一个整数,表示合并的最小花费。
这道题太过经典了。是四边形不等式的入门题。关于四边形不等式,这篇论文:https://wenku.baidu.com/view/c44cd84733687e21af45a906.html (动态规划加速原理之四边形不等式)讲述得深入浅出。我们知道本题dp的式子是
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
]
[
k
]
+
d
p
[
k
]
[
j
]
+
∑
l
r
a
[
t
]
)
(
k
∈
[
i
,
j
]
)
dp[i][j]=min(dp[i][k]+dp[k][j]+\sum^r_l{a[t]})(k\in [i,j] )
dp[i][j]=min(dp[i][k]+dp[k][j]+∑lra[t])(k∈[i,j]) 由于要枚举k,所以是三次方的效率。但由于权值
∑
l
r
a
[
t
]
\sum^r_l{a[t]}
∑lra[t]满足文章中所说的区间单调性和四边形不等式,所以可以推出
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]同样满足四边形不等式,进而可以进行优化,优化的具体方式是设定
s
[
i
]
[
j
]
s[i][j]
s[i][j]为
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]最小时所取的k的位置,那么k的枚举范围可以缩小到
[
s
[
i
]
[
j
−
1
]
,
s
[
i
+
1
]
[
j
]
[s[i][j-1],s[i+1][j]
[s[i][j−1],s[i+1][j],经过累和计算可以知道这样的缩小范围可以把效率提升到O(n^2)。具体的细节太繁杂,还是参照论文。另外,相当多四边形不等式的递推式子还是需要证明的,因此,这篇论文有时间还是需要好好研究。
顺便提到一件事,最优矩阵链乘问题(MCM)的dp方法,是没有办法采用四边形不等式优化的,原因是递推式的权值项不仅和i,j有关,还和k有关系。但是MCM问题是有非dp解法的,可以达到O(nlogn),参见 http://www.cnki.com.cn/Article/CJFDTotal-CDQX401.005.htm(好吧我承认具体算法我还没看)
#include<cstdio>
using namespace std;
typedef long long LL;
int s[1005][1005],a[1005],n;
LL dp[1005][1005];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),s[i][i]=i,a[i]+=a[i-1];
for(int l=2;l<=n;l++)
for(int i=1,j;i<=n;i++)
{
j=i+l-1;
dp[i][j]=1E18;
for(int k=s[i][j-1];k<=s[i+1][j];k++)
if(dp[i][j]>dp[i][k]+dp[k+1][j]+a[j]-a[i-1])
{
dp[i][j]=dp[i][k]+dp[k+1][j]+a[j]-a[i-1];
s[i][j]=k;
}
}
printf("%lld",dp[1][n]);
return 0;
}