蒟蒻的9个背包的浩大工程(更新中)

Posted ywjblog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了蒟蒻的9个背包的浩大工程(更新中)相关的知识,希望对你有一定的参考价值。

所以说背包直接看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大神博客。

dd神犇博客前往方式:

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

②    传送门:http://blog.csdn.net/ling_du/article/details/41594767

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

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

       for(int c=C;c>=0;c--)

              if()c>=w[i] 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=0;c<=C;c++)

              if(c>=w[i]) f[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]);

       }

}

以上是关于蒟蒻的9个背包的浩大工程(更新中)的主要内容,如果未能解决你的问题,请参考以下文章

[bzoj4636]蒟蒻的数列_线段树

2017 9 21

拯救蒟蒻的神器——快读

蒟蒻的模板

蒟蒻的2018停课集训总结

[日常]蒟蒻的高一生活 Week 4