【Algorithms】动态规划之区间DP

概论

所谓区间DP,从名字也可以看出与区间是密不可分的,也就是通过动态规划求得一段区间上最优解的算法,它的主要思想就是先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解。

一般算法格式

区间DP的算法格式比较固定,一般由数组dp[i][j]来表示在区间[i,j]内的最优解(具体由题目定义),然后由一个整形k(i<=k<j)来对区间[i,j]进行分割,一般分割为[i,k]和[k+1,j]两个区间,根据题目来确定它的状态转移方程。

for (int i = 1; i <= n; i++)                   //区间长度为1的初始化

    dp[i][i] = x;                               // x视情况而定

for (int len = 2; len <= n; len++)              //枚举区间长度

{

    for (int i = 1, j = len; j <= n; i++, j++)  //区间[i,j]

    {

        //DP方程实现

    }

}

例题

问题 Y: 【动态规划】合并魔法石I

时间限制: 1 Sec  内存限制: 64 MB

题目描述

太空梯的能量导轨上有n堆魔法石排成一排,其编号为1,2,3,…,n(n≤100)。每块魔法石有一定的数量,例如有7堆魔法石,分别为13,7,8,16,21,4,18。

现在要将n堆魔法石归并成为一堆。归并的过程为每次只能将相邻的两堆魔法石堆成一堆,这样经过n-1次归并后成为一堆,如上面的7堆魔法石,可以有多种方法归并成一堆,其中的两种方法如图所示。

则(a)的归并代价为20+24+25+44+69+87=267

(b)的归并代价为15+37+22+28+59+87=248

可见不同的过程得到的归并代价是不一样的,现在请编程找出一种合理的方法,使归并代价最小。

 

输入

第一行为一个整数n,且1<n≤100,表示有n堆沙子,第二行为n堆魔法石的数量。

 

输出

一个整数,即最小代价。

 

样例输入

7
13 7 8 16 21 4 18

 

样例输出

239

题目链接:http://exam.upc.edu.cn/problem.php?cid=1428&pid=24

分析:

这道题和区间DP的经典例题石子合并如出一辙,也都是将n堆石子通过合并为一堆求最后的最小花费。

要求n堆石子合并,根据动态规划的思想,我们可以将它划分为子问题n-1堆石子的合并,通过子问题求得最优解来一步步推导出最终的最优解,我们可以先求出2堆石子合并的花费,然后三堆石子合并可以分解为两堆石子与另一堆石子合并,这样通过前面求得的子问题最优解,最终就可以求得n堆石子合并所需的最小花费了。

设dp[i][j]为从第i堆石子到第j堆石子的合并,那么dp[i][j]就可以通过k来划分为dp[i][k]+dp[k+1][j]的问题了,这时候我们就可以写出它的状态转移方程了dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sumij),sumij为区间[i,j]内合并石子的总花费,可以提前算出来减少时间复杂度(前缀和思想,牺牲空间获取时间)。

代码如下:

#include<bits/stdc++.h>

using namespace std;
int dp[101][101];
int sum[101];
int main()
{
    int n,v[101];
    cin>>n;
    memset(sum,0,sizeof(sum));
    memset(dp,0x3F,sizeof(dp));
    for(int i=1;i<=n;i++){
        cin>>v[i];
        sum[i]=sum[i-1]+v[i];
        dp[i][i]=0;
    }
    for(int len=2;len<=n;len++){
        for(int i=1,j=len;j<=n;i++,j++){
            int sumij=sum[j]-sum[i-1];
            for(int k=i+1;k<=j;k++){
                dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k][j]+sumij);
            }
        }
    }

//    for(int i=1;i<=n;i++){
//        for(int j=1;j<=n;j++){
//            printf("%15d",dp[i][j]);
//        }cout<<endl;
//    }
    cout<<dp[1][n]<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值