• 蒟蒻的9个背包的浩大工程(更新中)(无限延期)(太长了不舍删虽然写的lj的一匹)


    所以说这就是一篇写炸的废文!!!!

    所以说背包直接看dd大神的就好了,算了瞎写写吧。

    0/1背包

             有n件物品和一个容量为C的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品放入背包可使背包的价值总和最大。

           0/1背包是最基础的背包问题,问题的特点是:每种物品只有一件,那么对于第i种物品一共只存在两个状态:取或不取。

    定义状态:用f[i][c] 表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。

    那么很容易可以得到状态转移方程:

              f[i][j]=max{ f[i-1][c] , f[i-1][c-w[i]]+v[i] }

    这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。

    如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v]; 如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。(以上两段话来自dd神犇博客)

    据dd神犇所讲,对于上述方程,时间复杂度和空间复杂度为O(N*V),时间复杂度已经无法优化,而空间复杂度可以优化;具体操作详见dd大神博客。

    ①    百度—搜索背包九讲—找一个写的最强大的CSDN博客

    ②    传送门:http://blog.csdn.net/ling_du/article/details/41594767(搬运)

    回到正题,虽然我是讲不清楚(早晚弄懂回来讲一遍)代码还是要放的

    for(int i=1;i<=n;i++){

           for(int c=C;c>=w[i];c--)

                 f[c]=max(f[c],f[c-w[i]]+v[i]);

    }

    需要注意的是,第二层循环的顺序十分重要,如果写成顺推,就会出现已经更新过的状态被重复更新,实际上这可以用来解决完全背包,那么如何才能让每个状态只被更新一次?逆推。我们想一下,我们所写的方程,利用的是c-w[i],如果正着推,不可避免的会出现c-w[i]=之前被更新过的某个状态,但倒着显然就不会,因为c-w[i]是越变越小的,这样就达到了每个物品只取一次的目的。当然…这是我yy+显然的证明法,具体解释还是去dd神犇博客慢慢看吧。

    完全背包

    有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

    对于完全背包,它和0/1背包的区别就在于0/1背包的每件物品只能用一次,而完全背包每件物品可以使用无限次。基本解法就是将它转变成0/1背包来解,相较于0/1背包,似乎要多枚举一层件数?

    状态转移方程:

           f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}

    这跟01背包问题一样有O(N*V)个状态需要求解,但求解每个状态的时间已经不是常数了,求解状态f[i][v]的时间是O(v/c[i]),总的复杂度是超过O(VN)的。

    将01背包问题的基本思路加以改进,得到了这样一个清晰的方法。这说明01背包问题的方程的确是很重要,可以推及其它类型的背包问题。但我们还是试图改进这个复杂度。(来自dd神犇)。

           还记得0/1背包中被我yy+显然得出的结论吧…..

    没错!0/1背包简化成一维后的第二重循环我们只要把逆推改为正推就行!既然完全背包需要重复计算,那我们就用曾经被我们否决掉的能用来重复计算的方案就好。(更严谨的解释…你们知道我要让你们看什么)。

    代码:

    for(int i=1;i<=n;i++){

           for(int c=w[i];c<=C;c++)

                  if[c]=max( f[c] , f[c-w[i]] + v[i]);

    }

    /*来自dd神犇博客:完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。这个优化的正确性显然:任何情况下都可将价值小费用高得j换成物美价廉的i,得到至少不会更差的方案。对于随机生成的数据,这个方法往往会大大减少物品的件数,从而加快速度。然而这个并不能改善最坏情况的复杂度,因为有可能特别设计的数据可以一件物品也去不掉。

    这个优化可以简单的O(N^2)地实现,一般都可以承受。另外,针对背包问题而言,比较不错的一种方法是:首先将费用大于V的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的是哪个,可以O(V+N)地完成这个优化。*/

    多重背包

           有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

           和完全背包类似的问题,除了每件物品有固定件数,循环改一下即可,方程:

    f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}

    没错就只是加了一个数量限制而已。

    然后我们想着试图优化这个方程。

    转化为01背包问题

    另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为Σn[i]的01背包问题,直接求解,复杂度仍然是O(V*Σn[i])。

    但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。

    方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。

    分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出,例如:

           13=2^0+2^1+2^2+6

    对于件数足够多的,我们可以将它视为完全背包来写。代码:

    for(int i=1;i<n;i++){

           if(w[i]*a[i]>C){

                  for(int c=0;c<=C;c++)

                         if(c>=w[i])

                                f[c]=max(f[c],f[c-w[i]]+v[i]);

           }

           else{

                  int k=1,amount=a[i];

                  while(k<amount){//想一想刚才说的二进制法

                         for(int c=C;c>=k*w[i];c--)

                                f[c]=max(f[c],f[c-k*w[i]]+k*v[i]);

                         amount-=k;

                         k+=k;

                  }

                  for(int c=C;c>=amount*w[i];c--)//把剩下的当成一个物品

                         f[c]=max(f[c],f[c-amount*w[i]]+amount*v[i]);

           }

    }

    混合背包

    简而言之,将以上三种背包混在一起,判断一下是哪个背包即可

    for(int i=1;i<=n;i++){
      if(num[i]==1){//0/1
        for(int c=v1;c>=w[i];c--)
          f[c]=max(f[c],f[c-w[i]]+v[i]);
      }
      else if(num[i]*w[i]>v1){//完全
        for(int c=w[i];c<=v1;c++)
          f[c]=max(f[c],f[c-w[i]]+v[i]);
      }
      else {//多重
        int k=1,amount=num[i];
        while(k<amount){
          for(int c=v1;c>=k*w[i];c--)
            f[c]=max(f[c],f[c-k*w[i]]+k*v[i]);
          amount-=k;
          k<<=1;
        }
        for(int c=v1;c>=amount*w[i];c--)
        f[c]=max(f[c],f[c-amount*w[i]]+amount*v[i]);
      }
    }

     

  • 相关阅读:
    17-canvas绘制扇形
    16-canvas绘制圆弧
    15-canvas渐变色
    14-canvas绘制柱状图
    13-绘制矩形的简写方式
    12-es6类的方式封装折线图
    11-canvas绘制折线图
    10-canva绘制数据点
    jenkins 环境部署 (yum安装方式)
    BerkeleyDB安装
  • 原文地址:https://www.cnblogs.com/ywjblog/p/8480272.html
Copyright © 2020-2023  润新知