1.状态定义
f[i][j] : (1) 集合: 所有将第 i 堆到第 j 堆石子合并成一堆石子的合并方式
(2) 属性:所有合并方式中代价的最小值
2.状态计算
以最后一步合并的分界线的位置来划分,如
左边1堆,右边k - 1堆
左边2堆,右边k - 2堆
左边k - 1堆,右边1堆
最后一步的合并代价为整个区间的和,因此我们可以利用前缀和解决最后一步。
曲线救国的方式,我们可以先去掉最后一步,找最小值,再加上最后一步
以k作为分界点:先求左半部分的最小代价 + 右半部分的最小代价 + 最后一步整个区间的代价
f[i][j] = min(f[i][j],f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
我们可以枚举每个分类中找代价最小的,最终f[i][j] 取所有情况的最小值。
注意:保证计算每一个f[i][j]时,提前算好他所依赖的状态;
计算顺序取决于区间长度。
!区间长度为1时合并代价为 0 !
C++代码
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int n;
int s[N];
int f[N][N];
int main(){
cin >> n;
for(int i = 1; i <= n; i++) cin >> s[i];
for(int i = 1; i <= n; i++) s[i] += s[i-1]; //求前缀和 - 最后一步
//len = 1 ,区间长度为1时,合并代价为0
for(int len = 2; len <= n; len ++){
for(int i = 1; i + len - 1 <= n; i ++){ //枚举最短点
int l = i, r = i + len - 1; //左右端点
f[l][r] = 1e8;
for(int k = l; k < r; k++){
f[l][r] = min(f[l][r],f[l][k] + f[k + 1][r] + s[r] - s[l-1]);
}
}
}
cout << f[1][n]; //从第1堆合并到第n堆的最小合并代价
return 0;
}