动态规划-01背包问题

动态规划-01背包问题

背包问题是一类经典的动态规划问题,它非常灵活,变化多样,需要仔细体会。本书只介绍两类最简单的背包问题:01背包问题和完全背包问题,而这两种背包中,又以01背包为重。

  1. 多阶段动态规划问题

    有一类动态规划可解的问题,它可以描述成若干个有序的阶段,且每个阶段的状态只和上一个阶段的状态有关,一般把这类问题称为多阶段动态规划问题。01背包问题就是这样一个例子。

  2. 01背包问题

    01背包问题是这样的:

    有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都只有1件。

    样例
    5 8
    3 5 1 2 2	// w[i]
    4 5 2 1 3	// c[i]
    

在这里插入图片描述

dp[i][v]表示前i件物品(1 <= i <= n ;,0 <= v <= V)恰好装入容量为v的背包中所能获得的最大价值。怎么求解dp[i][v]呢?

考虑对第i件物品的选择策略,有两种策略:

  1. 不放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v的背包中所能获得的最大价值,也即dp[i-1][v]
  2. 放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,也即dp[i-1][v-w[i]]+c[i]

由于只有这两种策略,且要求获得最大价值,因此
在这里插入图片描述

上面这个就是状态转移方程。注意到dp[i][v]只与之前的状态dp[i-1][]有关,所以可以枚举i从1到n,v从0到V,通过边界dp[0][v] = 0 (0 <= v <= V)(即前0件物品放入任何容量为v的背包中都只能获得价值0)就可以把整个dp数组递推出来。而由于dp[i][v]表示的恰好为v的情况,所以需要枚举dp[n][v](0<=v<=V),取其最大值才是最后的结果。

for(int i = 1 ; i <= n ; i++){
    for(int v = w[i] ; v <= V ; v++){
        dp[i][v] = max(dp[i-1][v],dp[i-1][v-w[i]]+c[i])
    }
}

在这里插入图片描述
这样修改对应到图中可以这样理解:v的枚举顺序变为从右往左,dp[i][v]右边的部分为刚计算过的需要保存给下一行使用的数据,而dp[i][v]左上角和右边的部分放在一个数组里,每计算出一个dp[i][v],就相当于把dp[i-1][v]抹消,因为在后面的运算中dp[i-1][v]再也用不到了。我们把这种技巧称为滚动数组。

代码如下:

for(int i = 1 ; i <= n ; i++){
    for(int v = V ; v >= w[i] ; v--){// 逆序枚举v
        dp[v] = max(dp[v],dp[v-w[i]]+c[i]);
    }
}

这样01背包问题就可以用一维数组表示来解决了,空间复杂度为O(V)。

特别说明:如果是用二维数组存放,v的枚举是顺序还是逆序都无所谓;如果使用一维数组存放,则v的枚举必须逆序!

  1. 完整的求解01背包问题的代码如下:

    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    const int maxn = 100;	// 物品最大件数
    const int maxv = 1000;	// V的上限
    
    int w[maxn],c[maxn],dp[maxv];
    
    int main(){
        int n,V;
        scanf("%d%d",&n,&V);
        for(int i = 0 ; i < n ; i++){
            scanf("%d",&w[i]);
        }
        for(int i = 0 ; i < n ; i++){
            scanf("%d",&c[i]);
        }
        // 边界
        for(int v = 0 ; v <= V ; v++){
            dp[v] = 0;
        }
        for(int i = 1 ; i <= n ; i++){
            for(int v = V ; v >= w[i] ; v--){
                // 状态转移方程
                dp[v] = max(dp[v],dp[v-w[i]]+c[i]);
            }
        }
        // 寻找dp[0...V]中最大的即为答案
        int max = 0;
        for(int v = 0 ; v <= V ; v++){
            if(dp[v] > max){
                max = dp[v];
            }
        }
        printf("%d\n",max);
        return 0;
    }
    
  2. 输入样例数据

    5 8
    3 5 1 2 2
    4 5 2 1 3
    
  3. 运行结果

在这里插入图片描述

  1. 动态规划是如何避免重复计算的问题在01背包中非常明显。在一开始暴力枚举每件物品放或者不放入背包时,其实忽略了一个特性:第i件物品放或者不放而产生的最大值是完全可以由前面i-1件物品的最大值来决定的,而暴力做法无视了这一点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值