一、简单石子合并
1. 题目描述
有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
2. 分析
2.1 状态表示——化零为整
问题要求的是所有合并方式里面体力最小的方案。所有的合并方式一共是 ( n − 1 ) ! (n-1)! (n−1)! 种方案。
2.1.1 集合
所有满足条件一,条件二的元素的集合。所谓的条件一,条件二是DP数组的两个维度。
因此,f(i,j)
表示所有将[i,j]
合并为一堆的方案的集合。表示了
(
j
−
i
)
!
(j-i)!
(j−i)!种方案。
2.1.2 属性
集合里面最小的代价
2.2 状态计算——化整为零
需要将集合f(i)
进行划分(即化整为零)。所谓划分就是寻找最后一个不同点。
对于石子合并问题,是根据最后一次合并的是哪两个左右子区间进行划分的。
区间DP的枚举套路一般都是:
- 枚举区间长度;
- 枚举区间左端点;
- 然后根据区间内的状态转移方程进行不同的求解。
3. 代码实现
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 310;
const int INF = 0x3fffffff;
int n;
int dp[maxn][maxn];
int sum[maxn];
int main(){
scanf("%d",&n);
sum[0] = 0;
for(int i=1;i<=n;i++){
int cur;
scanf("%d",&cur);
sum[i] = sum[i-1]+cur;
}
//初始化dp数组
fill(dp[0],dp[0]+maxn*maxn,INF);
for(int i=0;i<=n;i++){
dp[i][i] = 0;
}
//区间dp的做法就是枚举区间长度
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j = i+len-1;
//枚举左边的最后一个元素
for(int k=i;k<j;k++){
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
}
}
}
printf("%d",dp[1][n]);
return 0;
}