动态规划之区间DP详解

该博客介绍了动态规划在解决区间合并问题中的应用,通过两个典型例题展示了如何使用区间DP解决石子合并问题。例题一是线性石子合并,例题二是环形石子合并。在环形问题中,通过复制数组将环形转换为链式,然后进行DP求解。博客还提供了完整的C++代码实现。
摘要由CSDN通过智能技术生成


什么是区间DP?

顾名思义:区间dp就是在区间上进行动态规划,求解一段区间上的最优解。主要是通过合并小区间的 最优解进而得出整个大区间上最优解的dp算法。

典型例题一:石子合并

1.1 题目:

题目链接

1.2 分析:

1.2.1 状态表示:
f[l,r]表示把从L到R合并成一堆的最小代价。
1.2.2 状态转移:

  1. 先把区间[L,R]切分成两部分[L,K], [K+1,R], k是切分点;
  2. 再把两部分[L,R]和[k+1,R]合并在一起。(利用前缀和求区间和)

在这里插入图片描述
转移:
f[L,k] + f[k+1,R] + s[R] - s[L-1] ——>f[L,R]
计算:
f[L,R]=min(f[L,R] , f[L,k] + f[k+1,R] + s[R] - s[L-1])

1.2.3 边界情况:
f[i,i]=0(合并每堆石子的代价为0),其余为正无穷。

1.3 完整代码:

// 算法:区间DP
// 时间:2022.07.13 20点06分
#include<iostream>
#include<algorithm>
using namespace std;

const int N=310;
int n;//石子堆数
int s[N];//记录前缀和 用来求合并时候花费的代价
int f[N][N];//f[i][j]表示把从l到r合并成一堆的最小代价
int main ()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>s[i];
    //求前缀和
    for(int i=1;i<=n;i++) s[i]+=s[i-1];
    
    for(int len=2;len<=n;len++)//阶段:枚举区间长度
        for(int i=1;i+len-1<=n;i++)//状态:枚举区间起点
            {
                int l=i,r=i+len-1;//区间的左右端点
                f[l][r]=1e9;
                for(int k=l;k<r;k++)//决策:枚举分割点
                //枚举最后一次合并的分界点
                    f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
            }
    cout<<f[1][n]<<endl;
    return 0;

}

典型例题二:环形石子合并

2.1 题目:

在这里插入图片描述

2.2 分析:

这个问题只是将例题一的链式石子合并转换为环形,那么我们首先可以想到将环形问题转换为链式。对于环上的每一条边进行切割将其变为链式,所以只需要再多加一层循环即可。
在这里插入图片描述

核心代码:

for(int j=1;j<=n;j++)//枚举环形的缺口
    for(int len=2;len<=n;len++)//阶段:枚举区间长度
        for(int i=1;i+len-1<=n;i++)//状态:枚举区间起点
            {
                int l=i,r=i+len-1;//区间的左右端点
                f[l][r]=1e9;
                for(int k=l;k<r;k++)//决策:枚举分割点
                //枚举最后一次合并的分界点
                    f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
            }

但是显而易见时间复杂度是O(n4),所以会TLE,那么如何进行优化呢??
这里提供一种常见的环形问题转换为链式的一种方法:
复制一遍数组,转化为长度为2N的链形数组。
在这里插入图片描述
核心代码:


//链形石子合并模板
for(int len=2;len<=n;len++)//阶段:枚举区间长度
    for(int l=1;l+len-1<=2*n;l++){//状态:枚举区间起点
        int r=l+len-1;//区间终点
        for(int k=l;k<r;k++)//决策:枚举分割点
            f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
    }

2.3 完整代码:

#include<bits/stdc++.h>
using namespace std;

const int N=110,INF=0x3f3f3f3f;
int n;
int a[N];
int s[N];//记录前缀和
int f[N][N];//f[l][r]表示把从l到r合并成一堆的得分的最小值
int g[N][N];//g[l][r]表示把从l到r合并成一堆的得分的最大值
int main ()
{
    memset(f,INF,sizeof f);
    memset(g,-INF,sizeof g);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        a[i+n]=a[i];//复制一遍区间
    }
    for(int i=1;i<=2*n;i++){
        s[i]=s[i-1]+a[i];//前缀和
        g[i][i]=0,f[i][i]=0;
    }
    //DP
    for(int len=2;len<=n;len++)
        for(int l=1;l+len-1<=2*n;l++){
            int r=l+len-1;
            for(int k=l;k<r;k++){
                f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
                g[l][r]=max(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);
            }
        }
    //输出
    int minv=INF,maxv=-INF;
    for(int i=1;i<=n;i++){
        minv=min(minv,f[i][i+n-1]);
        maxv=max(maxv,g[i][i+n-1]);
    }
    cout<<minv<<endl<<maxv<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值