区间动态规划详解

前段时间遇到石子合并问题,看着题解A了,以为学会了区间DP,再次遇到能量项链这个问题的时候大脑还是一片空白,只能重新认识一下区间动态规划了。

翻过很多博客,基本上都是题解,真正对区间动态规划本身的讲解几乎没有,可能是我没找到吧。

 

区间动态规划,顾名思义,就是动态规划过程中求一个区间的最优解。通过将一个大的区间分为很多个小的区间,求其小区间的解,然后一个一个的组合成一个大的区间而得出最终解,有没有发现,这完全就是分治的思想。

 

动态规划三部曲:

1.状态:用dp[i][j]表示区间(i,j)的最优解

2.状态转移:

        最常见的写法:     dp[i][j]=max/min(dp[i][j],dp[i][k]+dp[k+1][j]+something)

        理解:区间(i,j)的最优解就等于 区间(i,j)   同 (i,k)和区间(k+1,j)合并后的值   比谁更优。

3.初始化:           dp[i][i]=0/other  (i=1->n)

     为什么这么做呢?很好理解,就是只包含自己本身的时候,也就是区间长度为1,值肯定是确定的。

 

怎么从小到大的合并区间呢?

有两种最常见的写法:

第一种:

 for(r=2;r<=n;r++)//r:区间右端点,l:区间左端点
        for(l=r-1;l>=1;l--)
            for(k=l;k<r;k++)
                dp[l][r] = max/min(dp[l][r],dp[l][k]+dp[k+1][r]+something);

第二种:

for (int len=2;len<=n;len++)//len:长度 ,l:左区间断点,r:又区间端点
    for (int l=1;l+len-1<=n;l++) //保证(l,r)的长度为len,且不越界
    {
        int r=l+len-1;
        for (int k=l;k<=r;k++)
            dp[l][r]=max/min(dp[l][r],dp[l][k]+dp[k+1][r]+something)
    }

第一种是个人比较喜欢也是很容易想到的。理解起来很简单:遍历以r为终点,从1到r的所有子区间。

这种做法的一个好处就是dp[l][r]存的一定是r为右区间的每一步的最优解。

第二种是用的人数最多的。 理解:从区间长度为2--n,每次遍历固定长度的区间。比如,len=2时,遍历(1,2)(2,3)..(n-1,n)。这样也能遍历完所有的情况。

 

当然还有其他写法,例如:

    for (int len=1;len<=n;len++) //枚举长度
        for (int l=1;l<n-len;l++) //从第i个石子开始
            for (int j=l;j<l+len;j++)
                dp[l][l+len]=max(dp[l][l+len],dp[l][j]+dp[j+1][l+len])+somethong;

 思维都大同小异,只要保证是从小区间遍历到大区间就没问题。

 

再来看石子合并问题

题目描述

在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

输入输出格式

输入格式:

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

输出格式:

输出共2行,第1行为最小得分,第2行为最大得分.

 

输入输出样例

输入样例#1:

4
4 5 9 4

输出样例#1: 

43
54

题解:

第一眼看上去就是个贪心问题,每次合并最大的两个,这么想就错了。

8 4 6 3 5   贪心的结果为62,而最优解为60

 

每次进行合并的时候合并一个区间。
设dp[i][j]为合并i-j区间的石子的最优方案即最小花费,用k去划分石子数去进行状态转移即可。

我们可以得到状态转移方程:
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])

 

sum[i]表示前i个石子分数之和

#include<iostream>
using namespace std;
int f1[220][220],f2[220][220];
int main()
{
    int n,i,j,k;
    int a[210];
    int sum[220]={0};
    cin>>n;
    for(i=1;i<=n;i++)
        cin>>a[i],a[i+n]=a[i];
    for(i=1;i<=n*2;i++)
        sum[i]=sum[i-1]+a[i];


    for(int len=2;len<=n;len++)
    {
        for(i=1;i<=2*n-len+1;i++)
        {
            j=i+len-1;
            f1[i][j]=0x3f3f3f3f;
            f2[i][j]=0;
            for(k=i;k<j;k++)
            {
                f1[i][j]=min(f1[i][k]+f1[k+1][j],f1[i][j]);
                f2[i][j]=max(f2[i][k]+f2[k+1][j],f2[i][j]);
            }
            f1[i][j]+=sum[j]-sum[i-1];
            f2[i][j]+=sum[j]-sum[i-1];
        }
    }

    int M = -1;
    int m = 0x3f3f3f3f;
    for(i=1;i<=n;i++)
    {
        if(f1[i][i+n-1]<m) m=f1[i][i+n-1];
        if(f2[i][i+n-1]>M) M=f2[i][i+n-1];
    }
    cout<<m<<endl<<M;
    return 0;
}

代码写的有点复杂了,完全可以少一点行数。


注意一个问题:

为什么最优解在dp[i][i+n-1]的位置呢?

这么理解:dp[i][j]为(i,j)的最有解,即每次把最优解保存在区间最右端。

比如: i=1 :  dp[1][n-1]为区间(1,n-1)的最优解

            i=2 :  dp[2][n+1]为区间(2,n+1)的最优解(此题为环形转链,总长度2n)

 

区间DP的经典问题很多,比如矩阵连乘、整数划分、凸多边形划分、多边形合并、能量项链

最难的点还是状态转移方程

 

那么最后有一个问题,咱不可能每次都看了题解才恍然大悟吧。

究竟什么情况下用到区间动态规划?或者说区间动态规划有哪些应用场景呢?

笔者水平有限,暂未习得精华,待深入后再回来补充。

  • 15
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值