【背包九讲】01背包问题(dp+滚动数组)

一.01背包问题

现在有N件物品和一个容量为V的背包。第i件物品的费用是cost[i],价值是value[i]。每个物品最多只能选一次,求解在不超过背包容量的限制下,如何选取物品组合能使收益最大化?

题型有两种,一种要求背包恰好放满,一种不要求背包恰好放满

二.思路

现在考虑第二种题型,即不要求背包恰好放满

将问题分解成子问题:有i件物品,容量为j的背包。

用dp[ i ][ j ]数组存储子问题的答案。

假设现在要求取dp[ i ] [ j ] ,我们考虑要不要选取第i件物品:

①如果不选取,则dp[ i ] [ j ]=dp[ i-1 ][ j ],因为dp数组储存的一直是当前状态下最优的解,dp[ i-1 ][ j ]存放的值是在同样容量为j的背包限制下前i-1件物品最大的价值组合

②如果选取i件物品,考虑第i件物品的花费cost[ i ],则dp[ i ][ j ]=dp[ i-1] [ j-cost[i] ] +value [ i ] , 而 dp[ i-1] [ j-cost[i] ] 存放的值是容量为j-cost[ i ]限制下, 前i-1件物品最大的价值组合

综上,得状态转移方程:

dp[ i ] [ j]=max(dp[ i-1 ][ j ], dp[ i-1] [j-cost[i] ] + value[ i ])

我们能保证不断得到每一个子问题的最优解,所以不断递推,即得到原问题的解

三.具体实现

由之前的思路可得,解每一个子问题dp[ i ][ j ]时,我们需要优先知道dp[ i-1 ][ j ]和dp[ i-1] [ j-cost[i] ] +value [ i ],观察可得必须先知道dp数组第i-1行的值

所有最外层循环让i从小到大递增

至于j是递增还是递减是无所谓的,因为求解子问题dp[ i ][ j ]时,我们只需要优先知道dp数组第i-1行的值,至于第i行的求解顺序是无所谓的

而考虑第一行,也就是只有一种物品时,那么j是从小到大还是从大到小都无所谓,因为第一行的dp值只取决于j是否大于cost[1]

后面几行都是第一行递推而来,第一行没问题后面几行自然没问题

    int dp[10][2005];
    memset(dp,0,sizeof(dp));

    for(int i=1;i<8;i++)
      for(int j=N;j>=1;j--){
           if(j>=cost[i])
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-cost[i]]+value[i]);
           else  //如果这个物品装不下那就直接不装
                dp[i][j]=dp[i-1][j];
      }

至于之前说的第一种题型,只要把dp数组初始化为负无穷即可

负无穷表示没有方案使该子问题得解

四.空间优化(滚动数组)

由之前的分析可以发现,求dp数组第i行的值时,只需要用到dp数组第i-1行的值,而其它几行的值都是用不到的,所以为了节省空间,只需要用一维的dp数组即可

但是要注意这其实是在用一维数组来模拟二维数组的情况,想象一个不断滚动的数组

所以这里的第二层循环中的j必须倒序了,不像之前是无所谓的了

原因很简单,我们需要dp[ i-1] [ j-cost[ i ] ]的值,而如果j顺序地从小到大增加则该值已经被覆盖了,那么该行后面的元素在计算时使用的就不是上一行的dp[ i-1] [ j-cost[ i ] ] ,其实已经被覆盖为dp[ i] [ j-cost[ i ] ]

代码:

int dp[2005];
    memset(dp,0,sizeof(dp));

    for(int i=1;i<8;i++)
      for(int j=N;j>=1;j--)
           if(j>=cost[i])
                dp[j]=max(dp[j],dp[j-cost[i]]+value[i]);
      

同时也不需要else dp[i][j]=dp[i-1][j];这句代码了 ,因为不变相当于不去覆盖上一行的dp值

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值