OJ:https://cn.vjudge.net/problem/UVALive-8177
简单翻译:
给你一堆石头,要求你每次只能将L到R堆石头合并成一堆,每个石头都有一个时间,每次合并的时间是合并的这些石头的时间总和。让我们求出将n个石头合并为一堆需要的最少的时间。
区间dp,顾名思义,就是解决一些区间内最优值的问题,通常的时间复杂度为n^2 或者 n^3
而区间dp的大致思路就是
首先确定状态
初始化长度为1(or 2,3....具体因题而异)的dp数组的值
然后枚举区间长度,枚举区间的起始点,(有的题目还需要枚举断点) 由小区间转移到大区间。
最后dp[1][n]往往就是答案。
回到正题,先给出状态转移方程
dp[i][j][k]:表示将i-j区间内的石头在变成K堆之前需要的最少时间
1.dp[i][j][1] = min(dp[i][j][e]+sum[j]-sum[i])
这个状态转移方程表达的是将区间i到j之间的石头合并为一堆,也就是真正的合并操作,L<=e<=R,sum表示前缀和。
为什么e要满足这个条件能理解吗?因为每次要求只能将L到R堆石头合并为一堆。
另外一个状态转移方程,这个方程不是合并,而是求和,利用小区间部分堆的时间消耗,求出大区间的时间消耗
2.dp[i][j][k] = min(dp[i][j][k],dp[i][e][1]+dp[e+1][j][k-1])
k>1 e表示i与j之间的一个点。注意,这是求和,并不是合并,因为k是大于1的,而合并每次要合并成一堆。
所以i到j区间内形成k堆,就需要更小的区间去和成k堆,因为是求和,所以不需要去满足L到R堆这个要求。
至于1和k-1,因为我们要求K堆的和,所以堆数肯定要是k。我们的目标是枚举出i到j这个区间内堆的个数为K的情况,所以我们确定一个区间的堆数,另外一个区间的堆的情况就确定了,就能枚举出所有情况了
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 101
#define min(x,y) x < y ? x : y
int main(){
int count[MAX];
int sum[MAX];
int dp[MAX][MAX][MAX];
int n,l,r,i,j,k,z;
while(scanf("%d%d%d",&n,&l,&r) == 3){
memset(dp,0xfff,sizeof(dp));
sum[0] = 0;
for(i=1;i<=n;i++){
scanf("%d",&count[i]);
sum[i] += count[i]+sum[i-1];
}
for(i=0;i<n;i++){
dp[i][i][1] = 0;
}
// 枚举每一个区间,从小到大
for(i=2;i<=n;i++){
for(j=1;j<n;j++){
// 终点
int e = i + j -1;
if(e > n){
break;
}
for(k=i;k>0;k--){
if(k == 1){
// 合并
for(z=l;z<=r;z++)
dp[j][e][1] = min(dp[j][e][1],dp[j][e][z]+sum[e]-sum[j-1]);
}else{
// 求和
for(z=j;z<=e;z++)
dp[j][e][k] = min(dp[j][e][k],dp[j][z][1]+dp[z+1][e][k-1]);
}
}
}
}
if(dp[1][n][1]!=0xfff){
printf("%d\n",dp[1][n][1]);
}else{
printf("0\n");
}
}
return 0;
}