【揹包】

首先说下动态规划,动态规划这东西就和递归一样,只能找局部关系,若想全部列出来,是很难的,比如汉诺塔。你可以说先把除最后一层的其他所有层都移动到2,再把最后一层移动到3,最后再把其余的从2移动到3,这是一个直观的关系,但是想列举出来是很难的,也许当层数n=3时还可以模拟下,再大一些就不可能了,所以,诸如递归,动态规划之类的,不能细想,只能找局部关系。


1.汉诺塔图片

(引至杭电课件:DP最关键的就是状态,在DP时用到的数组时,也就是存储的每个状态的最优值,也就是记忆化搜索)

要了解揹包,首先得清楚动态规划:

动态规划算法可分解成从先到后的4个步骤:

1. 描述一个最优解的结构;

2. 递归地定义最优解的值;

3. 以“自底向上”的方式计算最优解的值;

4. 从已计算的信息中构建出最优解的路径。

其中步骤1~3是动态规划求解问题的基础。如果题目只要求最优解的值,则步骤4可以省略。

揹包的基本模型就是给你一个容量为V的揹包

在一定的限制条件下放进最多(最少?)价值的东西

当前状态→ 以前状态

看了dd大牛的《揹包九讲》(点击下载),迷糊中带着一丝清醒,这里我也总结下01揹包,完全揹包,多重揹包这三者的使用和区别,部分会引用dd大牛的《揹包九讲》,如果有错,欢迎指出。(www.wutianqi.com留言即可)

首先我们把三种情况放在一起来看:

01揹包(ZeroOnePack): 有N件物品和一个容量为V的揹包。(每种物品均只有一件)第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入揹包可使价值总和最大。

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

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

比较三个题目,会发现不同点在于每种揹包的数量,01揹包是每种只有一件,完全揹包是每种无限件,而多重揹包是每种有限件。

——————————————————————————————————————————————————————————–

先来分析01揹包

01揹包(ZeroOnePack): 有N件物品和一个容量为V的揹包。(每种物品均只有一件)第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入揹包可使价值总和最大。

这是最基础的揹包问题,特点是:每种物品仅有一件,可以选择放或不放。

用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的揹包可以获得的最大价值。则其状态转移方程便是:

f[i][v]=max{ f [i-1][v] , f [i-1][v-c[i]]+w[i] }【注意:转移方程不包括第i个揹包】

把这个过程理解下:在前i件物品放进容量v的揹包时,

它有两种情况:

第一种是第i件不放进去,这时所得价值为:f[i-1][v]

第二种是第i件放进去,这时所得价值为:f[i-1][v-c[i]]+w[i]

(第二种是什么意思?就是如果第i件放进去,那么在容量v-c[i]里就要放进前i-1件物品)

最后比较第一种与第二种所得价值的大小,哪种相对大,f[i][v]的值就是哪种。

(这是基础,要理解!)

这里是用二位数组存储的,可以把空间优化,用一位数组存储。

用f[0..v]表示,f[v]表示把前i件物品放入容量为v的揹包里得到的价值。把i从1~n(n件)循环后,最后f[v]表示所求最大值。

*这里f[v]就相当于二维数组的 f[i][v]。那么,如何得到f[i-1][v]和f[i-1][v-c[i]]+w[i]?(重点!思考)
首先要知道,我们是通过i从1到n的循环来依次表示前i件物品存入的状态。即:for i=1..N
现在思考如何能在是f[v]表示当前状态是容量为v的揹包所得价值,而又使f[v]和f[v-c[i]]+w[i]标签前一状态的价值?

逆序!

这就是关键!

for i=1..N
   for v=V..0
        f[v]=max{f[v],f[v-c[i]]+w[i]};

分析上面的代码:当内循环是逆序时,就可以保证后一个f[v ]和 f[v-c[i]]+w[i] 是前面状态的!(ps:的确如此!,反过来想,假如顺序的话,那么就拿i=1时来讲,c[0]=1, c[1]=0, c[2]=0, c[3]=4, c[4]=4, c[5]=4, c[6]=8, c[7]=8, c[8]=8, c[9]=12, c[10]=12,明显是错的,而逆序就避免了这样情况~赞!)
这里给大家一组测试数据:

测试数据:
10,3
3,4
4,5
5,6


这个图表画得很好,借此来分析:

C[v]从物品i=1开始,循环到物品3,期间,每次逆序得到容量v在前i件物品时可以得到的最大值。(请在草稿纸上自己画一画

这里以一道题目来具体看看:

题目:http://acm.hdu.edu.cn/showproblem.php?pid=2602

代码:http://www.wutianqi.com/?p=533

分析:


具体根据上面的解释以及我给出的代码分析。这题很基础,看懂上面的知识应该就会做了。

——————————————————————————————————————————————————————————–

完全揹包:

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

完全揹包按其思路仍然可以用一个二维数组来写出:

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

同样可以转换成一维数组来表示:

伪代码如下:

for i=1..N
    for v=0..V
        f[v]=max{f[v],f[v-c[i]]+w[i]}

顺序!

想必大家看出了和01揹包的区别,这里的内循环是顺序的,而01揹包是逆序的。
现在关键的是考虑:为何完全揹包可以这么写?
在次我们先来回忆下,01揹包逆序的原因?是为了是max中的两项是前一状态值,这就对了。
那么这里,我们顺序写,这里的max中的两项当然就是当前状态的值了,为何?
因为每种揹包都是无限的。当我们把i从1到N循环时,f[v]表示容量为v在前i种揹包时所得的价值,这里我们要添加的不是前一个揹包,而是当前揹包。所以我们要考虑的当然是当前状态
这里同样给大家一道题目:

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1114

代码:http://www.wutianqi.com/?p=535(分析代码也是学习算法的一种途径,有时并不一定要看算法分析,结合题目反而更容易理解。)

——————————————————————————————————————————————————————————–

多重揹包

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

这题目和完全揹包问题很类似。基本的方程只需将完全揹包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的揹包的最大权值,则有状态转移方程:

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

这里同样转换为01揹包:

普通的转换对于数量较多时,则可能会超时,可以转换成二进制.


【关于多重揹包的优化,贴上DD大牛的原话:

仍然考虑二进制的思想,我们考虑把第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的四件物品,这样的4件物品可以组合成0~13。

分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分0..2^k-12^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一下。

这样就将第i种物品分成O(log n[i])种物品,将原问题转化为了复杂度为O(V*log n[i])01揹包问题,是很大的改进。

下面给出O(log amount)时间处理一件多重揹包中物品的过程,其中amount表示物品的数量:

procedure MultiplePack(cost,weight,amount)

    if cost*amount>=V

        CompletePack(cost,weight)

        return

    integer k=1

    while k<num

        ZeroOnePack(k*cost,k*weight)

        amount=amount-k

        k=k*2

    ZeroOnePack(amount*cost,amount*weight)

希望你仔细体会这个伪代码,如果不太理解的话,不妨翻译成程序代码以后,单步执行几次,或者头脑加纸笔模拟一下,也许就会慢慢理解了。

poj1014为例

代码这里


对于普通的。就是多了一个中间的循环,把j=0~bag[i],表示把第i中揹包从取0件枚举到取bag[i]件。

给出一个例题:

题目:http://acm.hdu.edu.cn/showproblem.php?pid=2191

代码:http://www.wutianqi.com/?p=537

补充:

分组揹包问题

问题

有N件物品和一个容量为V的揹包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入揹包可使这些物品的费用总和不超过揹包容量,且价值总和最大。


[编辑]算法

这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有:

   f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于组k}

使用一维数组的伪代码如下:


for k=1..n
   for v=V..0 // 注意容量放在费用外
       for 所有的i属于组k
           f[v]=max{f[v],f[v-c[i]]+w[i]}

注意这里的三层循环的顺序

"for v=V..0”这一层循环必须在“for 所有的i属于组k”之外

这样才能保证每一组内的物品最多只有一个会被添加到揹包中。原因可参考http://www.cppblog.com/Onway/articles/122695.html

小结

分组的揹包问题将彼此互斥的若干物品称为一个组,这建立了一个很好的模型。不少揹包问题的变形都可以转化为分组的揹包问题.


二维费用揹包问题

二维费用的揹包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有 一个可付出的最大值(揹包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和 b[i]。两种代价可付出的最大值(两种揹包容量)分别为V和U。物品的价值为w[i]。
费用加了一维,只需状态也加一维即可。设f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。状态转移方程就是:
f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}
如前述方法,可以只使用二维的数组:当每件物品只可以取一次时变量v和u采用逆序的循环,当物品有如完全揹包问题时采用顺序的循环。当物品有如多重揹包问题时拆分物品。


















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章