模型总结-修改

闫氏dp分析法

学算法就上AcWing,y总强!哈哈
用该方法解决dp问题时简介明了,即从集合角度思考dp问题。以下用01背包问题举例说明
图

在思考一个DP问题时可以从状态表示和状态计算两个角度思考问题,要从集合的角度思考问题即每一种状态代表一类情况。该方法的每个名词解释如下:
集合:即要考虑集合下标有几维组成(一般有低维到高维依次考虑),并确定每个维度代表的意义。
属性:每种状态存储值的意义,一般有max、min、数量这三种。
状态计算:将集合分为不同情况的几块并可以求出每块的值(可曲线救国),在从所有部分中求解最终值。

背包DP

背包模型是动态规划中很经典的一类模型,其问题一般可以描述为要购买多种物品求其最大价值、最大价值方案数、具体方案等。每个物品可能拥有多种费用(例如购买时对总体积、总重量、总价格有限制,即可抽象为三种费用),目标为总价值最高。一般背包问题编码思路如下:

初始化
for 枚举物品
	for 枚举费用
		枚举决策
输出答案

根据购买同种商品数量和商品组合限制可将背包模型分为以下5种类型。

01背包

顾名思义01背包只有两种状态,即买和不买。

例题:01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
图
一般背包问题均可降维储存,但这里为了便于理解只给出朴素版,降维储存请看空间优化

const int N=1009;
int dp[N][N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    int vi,wi;
    for(int i=1;i<=n;i++)
    {
        cin>>vi>>wi;
        for(int j=1;j<=m;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j-vi>=0)
                dp[i][j]=max(dp[i][j],dp[i-1][j-vi]+wi);
        }
    }
    
    cout<<dp[n][m];
    return 0;
}

例题及其变种:
1、普通01背包: acwing: 01背包问题
2、二维费用问题: acwing: 1022. 宠物小精灵之收服
3、方案数问题: acwing: 278. 数字组合
4、贪心+dp: acwing :能量石

完全背包

方案数问题: acwing: 1023. 买书
贪心+完全背包: acwing: 532. 货币系统

多重背包

组合背包

混合背包

几种常见问题

状态的费用限制

对于一种状态的费用有三种情况,费用不大于j的方案、费用等于j的方案、费用最小为j的情况。对于这三种情况的区别这里以01背包为例,其它背包问题大同小异。

1、体积不超过v时

例题:01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

分析:该题为01背包问题,f(i, j)表示在使用前 i 种物品时体积不超过 j 的最大值,在编码时无需特殊操作,初始化全为0即可

//注意这里为朴素写法没有做空间优化,dp数组为全局变量默认全部的值为0。
#include<bits/stdc++.h>
using namespace std;

const int N=1009;
int dp[N][N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    int vi,wi;
    for(int i=1;i<=n;i++)
    {
        cin>>vi>>wi;
        for(int j=1;j<=m;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j-vi>=0)
                dp[i][j]=max(dp[i][j],dp[i-1][j-vi]+wi);
        }
    }
    
    cout<<dp[n][m];
    return 0;
}

2、体积等于v时

例题: acwing1023. 买书

小明手里有n元钱全部用来买书,书的价格为10元,20元,50元,100元。
问小明有多少种买书方案?(每种书可购买多本)

分析:该题为01背包问题求方案数问题,dp(i , j)定义为使用前i中货币恰好能组成 j 面值的数量。初始化时只将合法状态置为合法值集合,不合法状态置为不合法值即可(一般但求最大值时,不合法值为-INF反之亦然,求方案数时置为1)

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

const int N=1009;
int dp[5][N];
int v[5]={0,10,20,50,100};

int main()
{
    int n;
    cin>>n;
    
    dp[0][0]=1//注意初始化
    
    for(int i=1;i<=4;i++)
    {
        for(int j=0;j<=n;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j-v[i]>=0) dp[i][j]+=dp[i][j-v[i]];
        }
    }
    cout<<dp[4][n];
    
    return 0;
}

3、体积最小为v时

例题: acwing1020. 潜水员

潜水员为了潜水要使用特殊的装备。他有一个带2种气体的气缸:一个为氧气,一个为氮气。
让潜水员下潜的深度需要各种数量的氧和氮。潜水员有一定数量的气缸。
每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。
他完成工作所需气缸的总重的最低限度的是多少?

分析:首先该题为二维费用问题,费用状态应该定义为最小使用数量。dp(i ,j ,k)表示在前 i 个气缸中选择氧气体积至少为 j ,氮气体积至少为 k 的最小重量。只需要在计算状态转移时将选择的负值状态置为0即可

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

int dp[1009][29][90];

int main()
{
    int m,n,k;
    cin>>m>>n>>k;
    
    memset(dp,0x3f,sizeof dp);//注意初始化
    dp[0][0][0]=0;
    
    int ai,bi,ci;
    for(int i=1;i<=k;i++)
    {
        cin>>ai>>bi>>ci;
        for(int j=0;j<=m;j++)
        {
            for(int l=0;l<=n;l++)
            {
                int &x=dp[i][j][l];
                x=dp[i-1][j][l];
                x=min(x,dp[i-1][max(0,j-ai)][max(0,l-bi)]+ci);//负值置0
            }
        }
    }
    cout<<dp[k][m][n];
    
    return 0;
}

方案数以及具体方案

关于求解方案数的问题大致分为,求解最大价值的方案数量、求解某个费用的方案数量、求解最大价值方案的具体组合

1、最大最大价值方案数量

例题: acwing:11. 背包问题求方案数
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。

分析:该题为01背包问题,首先需要求解出体积为 v 时的最大价值,在求解最大价值时维护一个数组记录转移状态即可。dp(i , j)表示只使用前 i 个物品,体积最大为 j 时的最大价值。g(i , j)表示只使用前 i 个物品,体积最大为 j 价值为dp(i , j)方案数量。

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

const int N=1009,MOD=1e9+7;
int dp[N][N],g[N][N];

int main()
{
    int n,v;
    cin>>n>>v;
    
    for(int i=0;i<=n;i++) g[i][0]=1;
    for(int i=1;i<=n;i++)
    {
        int vi,wi;
        cin>>vi>>wi;
        for(int j=0;j<=v;j++)
        {
            dp[i][j]=dp[i-1][j];
            g[i][j]=max(1,g[i-1][j]);
            if(j-vi>=0)
            {
                dp[i][j]=max(dp[i][j],dp[i-1][j-vi]+wi);
                if(dp[i-1][j-vi]+wi==dp[i-1][j]) g[i][j]=(g[i-1][j-vi]+g[i-1][j])%MOD;
                if(dp[i-1][j-vi]+wi>dp[i-1][j]) g[i][j]=g[i-1][j-vi];
            }
        }
    }
    
    cout<<g[n][v];
    
    return 0;
}

2、某个费用的方案数量

例题: acwing: 278. 数字组合
给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。

分析:该题时01背包模型,dp(i , j)表示只在前 i 个数中选择,值为 j 的方案个数。只需要dp数组0层合法状态置为1,状态转移时将每个方向上的方案数相加即可。

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

int dp[109][10009],w[109];

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>w[i];
    
    for(int i=0;i<=n;i++) dp[i][0]=1;
    
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j-w[i]>=0) dp[i][j]+=dp[i-1][j-w[i]];
        }
    }
    
    cout<<dp[n][m];
    return 0;
}

3、求解最大价值的具体方案组合

例题: acwing:12. 背包问题求具体方案
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1…N。

分析: 首先该题为01背包问题,让输出最小字典序的一个最大价值方案。这里我们可以采用反推状态转移的方法求解(注意字典序不是越长就越大)

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

const int N=1009;
int dp[N][N];
int v[N],w[N];

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    
    for(int i=n;i>0;i--)
    {
        for(int j=1;j<=m;j++)
        {
            dp[i][j]=dp[i+1][j];
            if(j-v[i]>=0) dp[i][j]=max(dp[i][j],dp[i+1][j-v[i]]+w[i]);
        }
    }
    

    int j=m;
    for(int i=1;i<=n;i++)
    {
        if(j-v[i]>=0&&dp[i][j]==dp[i+1][j-v[i]]+w[i])
        {
            j=j-v[i];
            cout<<i<<' ';
        }
    }
    
    return 0;
}

路径DP

序列DP

状态机DP

在某一类的DP问题中有很多状态类型并由一定的转换关系,这时我们就可以考虑能否采用状态机模型。

例题: acwing:1058. 股票买卖 V

给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

分析:受首先可以发现该题,有三个状态每种状态都有对应的转移方式,故这里可以采用状态机DP的求解方式求解。状态转移图如下:
在这里插入图片描述


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

int dp[100009][3];

int main()
{
    int n;
    cin>>n;
    
    int x;
    memset(dp,-0x3f,sizeof dp);
    dp[0][2]=0;
    for(int i=1;i<=n;i++)
    {
        cin>>x;
        dp[i][0]=max(dp[i-1][2]-x,dp[i-1][0]);
        dp[i][1]=dp[i-1][0]+x;
        dp[i][2]=max(dp[i-1][2],dp[i-1][1]);
    }
    
    cout<<max(dp[n][1],dp[n][2]);
    
    return 0;
}

例题及其变种:
1、acwing:1057. 股票买卖 IV
2、acwing:1052. 设计密码

状态压缩DP

区间DP

树形DP

数位DP

DFS记忆搜索实现DP

DP优化

空间优化

单调队列优化

斜率优化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值