UVA 8177 区间DP

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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值