常用背包小结

1.0-1背包

  0-1背包是有N种物品,每种物品只有一个,每种物品有它的花费和价值,可以选择拿或者不拿。

  dp[i][v]代表选择前i种物品背包容量为v时的最大价值。

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

  二维的话两重循环的顺序随便,因为dp[i-1][v-c[i]]在之dp[i][v]前肯定是已经计算过的。

  如果用一维,dp[v]代表选择前i个物品背包容量为v时的最大价值,就要这么写:

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

  v的循环要倒着写的原因:每种物品只有一个,如果顺着写,更新了某个dp[j],后面若v-c[i]=j,又用到了更新过的这个值,相当于一种物品不止被选择一次。一维必须要注意这个问题,二维就不用,因为就算前面选择了,更新了dp[i][j],也不会影响到dp[i-1][v-c[i]]。

  v的循环要放在内层的原因:不管一维二维,只要v倒着循环就要放在内层(一维的时候v必须倒着循环)。因为若把v放外层倒着循环,更新dp[i][v]的时候,dp[i-1][v-c[i]]还没有被计算过,相当于前面的选择对它根本没有影响,最后算出容量为v的结果就是最多从这N个物品当中选最优的一个。


2.完全背包

  完全背包是有N种物品,每种物品有无限个,每种物品有它的花费和价值,可以选择拿或者不拿。

  dp[i][v]代表选择前i种物品背包容量为v时的最大价值。

  也可以用二维表示,注意v的循环顺序是正的。

    for(int i=1;i<=N;i++)
        for(int v=c[i];v<=V;v++){
            dp[i][v]=max(dp[i][v],dp[i][v-c[i]]+w[i]);  //第i个物品可能已经拿了几个
            dp[i][v]=max(dp[i][v],dp[i-1][v-c[i]]+w[i]);    //第i个物品还没有拿
        }

  若用一维更简单,只是把0-1背包一维代码的v循环从倒着变成了正着。

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

  为什么这样可以,是因为第i个物品可以选择无限次,考虑加第i种物品时,正好需要可能已经加过第i种物品的dp[v-c[i]]的状态。


3.多重背包

  多重背包是有N种物品,每种物品有M个,每种物品有它的花费和价值,可以选择拿或者不拿。

  其实是可以对每种物品做M次0-1背包的,但是一般情况下很慢,要用一种二进制的方法。比如你有10张1元钱,为了避免做10次背包,你可以当作你有1张1元,1张2元,一张4元,最后剩10-1-2-4=3元。这样就可以表示出所有10张100能表示出来的数了,原理是二进制,10的二进制是1010,前3张是是肯定能组成8以下的了,用1010减去8以上10以下的数肯定不会大于8,所以若想表示8以上10以下的数只需要从全部钱中拿走那个减法得到的数。总之这种方法能组成所有本来面值能组成的数。

  poj1276是非常好的多重背包题。


4.分组背包

  分组背包是把一些物品分成M组,每组最多拿一个物品,每种物品有它的花费和价值,可以选择拿或者不拿。

  分组背包可以理解为对每组做0-1背包,把原来0-1背包的每个物品用现在每组的最优物品代替。

    for(int k=1;k<=M;i++)   //每组
        for(int v=V;v>=c[i];v--)
            for(int i=1;i<=n;i++) dp[v]=max(dp[v],dp[v-c[i]]+w[i]);     //物品i属于第k组

  这个就像是前面说的,把v放到了选择物品循环的外层,每组就只能选择一个物品了。注意若有c[i]为0的情况,就不能直接这样写了,以免一组被选入多个物品。写成二维的话v是可以正着循环的。

 

  若每组至少拿一个物品,就要把v的循环放在里面

    for(int k=1;k<=M;i++)   //每组
        for(int i=1;i<=n;i++)
            for(int v=V;v>=c[i];v--){
                if(dp[k][v-c[i]]!=-1) dp[k][v]=max(dp[k][v],dp[k][v-c[i]]+w[i]);     //前k组已经每组至少选一个
                if(dp[k-1][v-c[i]]!=-1) dp[k][v]=max(dp[k][v],dp[k-1][v-c[i]]+w[i]);    //前k-1组已经每组至少选一个
            }

  如果存在c[i]为0的情况,两个if的顺序不能交换,避免同一个物品加两次。

  这种情况暂时只做了hdu3033。


  树形DP貌似都是分组背包的思想,一般是把每个子节点当作一组,在每个子节点中选一种最优情况。


  目前先总结这么多,遇见什么好题再加。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值