学习笔记:四大背包问题详讲

今天介绍4种基本的背包,01背包,完全背包,多重背包,分组背包。

01背包

给一个容量m的背包,有n个物品,每个物品有w的重量和c的价值,求最大价值。
我们枚举每个物品,再枚举背包的重量,用一维数组dp[m]表示当前重量下最大价值。
注意;背包的重量需要逆推,才能使得装入的物品不重复,否则将会变成下面介绍的完全背包。
核心代码;

for(int i=1;i<=n;i++) 
    for(int j=m;j>=w[i];j--)           //逆推
      dp[j]=max(dp[j],dp[j-w[i]]+c[i]); 

状态转移方程中表示回到刚好容量能装这个物品时的最大价值+这个物品的价值=当前的最大价值,和就不装这个物品做比较。

完全背包

题目大意与上面类似,不过每个物品有无数个,可以重复装。
我们已经提到,如果正推那么就会产生重复,那么直接改成正推即可。

for(int i=1;i<=n;i++) 
    for(int j=w[i];j<=m;j++)           //正推
      dp[j]=max(dp[j],dp[j-w[i]]+c[i]); 

状态转移与上文类似。

多重背包

还是有一个容量为m的背包,有n种物品,每种物品有x个,每个的重量为w,价值为c,求最大价值。
暴力拆分:
我们不管是不是一种,全部看做独立的每一个物品,直接01背包解决,但时间复杂度太高,显然不行,那如何优化。

for(int i=1;i<=n;i++)
   for(int k=1;k<=x[i];k++) //枚举每一个物品
    for(int j=m;j>=w[i];j--)
      dp[j]=max(dp[j],dp[j-w[i]]+c[i]);

我们发现,如果我一种物品要拿多个,必须一个一个拿,浪费时间,如果我能一次拿多个不就行吗,那么我们需要一种优美的拆分方法。
引入二进制拆分
一定存在数k,使得20+21+22+…+2k<=x[i](物品数),剩余的用r=x[i]-20+21+22+…+2k表示,例如x[i]=7,可以拆为1,2,4(k+1个),x[i]=9,可以拆成1,2,4,2(k+2个)。
而且可以发现,这拆分后的数可以组成1~x[i]的任意一个数,那么要取多少个,都有一种组合可以构成。
如何得到2k可以使用位运算按位<<处理,拆分后,我们即可把每一堆看作一个整体进行01背包。
核心代码:

for(int j=1;j<=x;x-=j,j<<=1)        //二进制拆分x 
    {
    
    
            for(int k=m;k>=j*w;k--)//拆一坨就作一坨
                dp[k]=max(dp[k],dp[k-j*w]+j*c);
        }
        if(x!=0)                           //多余的再01背包
    {
    
    
            for(int k=m;k>=x*w;k--)
               dp[k]=max(dp[k],dp[k-x*w]+x*c);
        }

分组背包

有n组物品,每组物品有x个,第i个重量为w,价值为c。背包还是背包,但每组物品只能选一个,求最大价值。

我们只需要枚举一下每组物品中每个物品,但是我们需要注意不能重复拿一组里面的物品,不然就是多重背包了,解决办法很简单,把多重背包暴力拆分代码中枚举每个物品的循环放到最里面即可。

for(int i=1;i<=n;i++)
    for(int j=m;j>=0;j--)
      for(int k=1;k<=x[i];k++)
        if(j>=w[i][k])
          dp[j]=max(dp[j],dp[j-w[i][k]]+c[i][k]);

四种基本背包就介绍完了。

Guess you like

Origin blog.csdn.net/pigonered/article/details/120865547