好文档 - 专业文书写作范文服务资料分享网站

背包九讲 2.0

天下 分享 时间: 加入收藏 我要投稿 点赞

你会发现,这个伪代码与01背包问题的伪代码只有v的循环次序不同而已。

为什么这个算法就可行呢?首先想想为什么01背包中要按照v递减的次序来循环。让v递减是为了保证第i次循环中的状态F[i,v]是由状态F[i?1,v?Ci]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果F[i?1,v?Ci]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果F[i,v?Ci],所以就可以并且必须采用v递增的顺序循环。这就是这个简单的程序为何成立的道理。

值得一提的是,上面的伪代码中两层for循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。

这个算法也可以由另外的思路得出。例如,将基本思路中求解F[i,v?Ci]的状态转移方程显式地写出来,代入原方程中,会发现该方程可以等价地变形成这种形式:

F[i,v]=max(F[i?1,v],F[i,v?Ci]+Wi)

将这个方程用一维数组实现,便得到了上面的伪代码。最后抽象出处理一件完全背包类物品的过程伪代码:defCompletePack(F,C,W)forv=CtoV

F[v]=max{F[v],f[v?C]+W}

2.6小结

完全背包问题也是一个相当基础的背包问题,它有两个状态转移方程。希望你能够对这两个状态转移方程都仔细地体会,不仅记住,也要弄明白它们是怎么得出来的,最好能够自己想一种得到这些方程的方法。

事实上,对每一道动态规划题目都思考其方程的意义以及如何得来,是加深对动态规划的理解、提高动态规划功力的好方法。

3

3.1

多重背包问题

题目

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

3.2基本算法

这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可。

因为对于第i种物品有Mi+1种策略:取0件,取1件……取Mi件。令F[i,v]表示前i种物品恰放入一个容量为v的背包的最大价值,则有状态转移方程:

F[i,v]=max{F[i?1,v?k?Ci]+k?Wi|0≤k≤Mi}

复杂度是O(VΣMi)。

6

3.3转化为01背包问题

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

但是我们期望将它转化为01背包问题之后,能够像完全背包一样降低复杂度。

仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0...Mi件——均能等价于取若干件代换以后的物品。另外,取超过Mi件的策略必不能出现。

方法是:将第i种物品分成若干件01背包中的物品,其中每件物品有一个系数。这件物品的费用和价值均是原来的费用和价值乘以这个系数。令这些系数分别为1,2,22...2k?1,Mi?2k+1,且k是满足Mi?2k+1>0的最大整数。例如,如果Mi为13,则相应的k=3,这种最多取13件的物品应被分成系数分别为1,2,4,6的四件物品。

分成的这几件物品的系数和为Mi,表明不可能取多于Mi件的第i种物品。另外这种方法也能保证对于0...Mi间的每一个整数,均可以用若干个系数的和表示。这里算法正确性的证明可以分0...2k?1和2k...Mi两段来分别讨论得出,希望读者自己思考尝试一下。

这样就将第i种物品分成了O(logMi)种物品,将原问题转化为了复杂度为O(VΣlogMi)的01背包问题,是很大的改进。

下面给出O(logM)时间处理一件多重背包中物品的过程:

defMultiplePack(F,C,W,M)ifC·M≥V

CompletePack(F,C,W)returnk:=1

whilek

ZeroOnePack(kC,kW)M:=M?kk:=2k

ZeroOnePack(C·M,W·M)

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

3.4O(VN)的算法

多重背包问题同样有O(VN)复杂度的算法。这个算法基于基本算法的状态转移方程,但应用单调队列的方法使每个状态的值可以以均摊O(1)的时间求解。我最初了解到这个方法是在楼天成的“男人八题”幻灯片上。(TODO:是否在此插入单调队列的讲解呢?)

3.5小结

在这一讲中,我们看到了将一个算法的复杂度由O(VΣMi)改进到O(VΣlogMi)的过程,还知道了存在复杂度为O(VN)的算法。

希望你特别注意“拆分物品”的思想和方法,自己证明一下它的正确性,并将完整的程序代码写出来。

7

4

4.1

混合三种背包问题

问题

如果将前面1、2、3中的三种背包问题混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?

4.201背包与完全背包的混合

考虑到01背包和完全背包中给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)。伪代码如下:

fori=1toN

if第i件物品属于01背包forv=VtoCi

F[v]=max(F[v],F[v?Ci]+Wi)elseif第i件物品属于完全背包forv=CitoV

F[v]=max(F[v],F[v?Ci]+Wi)

4.3再加上多重背包

如果再加上最多可以取有限次的多重背包式的物品,那么利用单调队列,也可以给出均摊O(VN)的解法。

但如果不考虑单调队列算法的话,用将每个这类物品分成O(logMi)个01背包的物品的方法也已经很优了。

当然,最清晰的写法是调用我们前面给出的三个过程。

fori=1toN

if第i件物品属于01背包ZeroOnePack(F,Ci,Wi)elseif第i件物品属于完全背包CompletePack(F,Ci,Wi)elseif第i件物品属于多重背包MultiplePack(F,Ci,Wi,Ni)

在最初写出这三个过程的时候,可能完全没有想到它们会在这里混合应用。我想这体现了编程中抽象的威力。如果你一直就是以这种“抽象出过程”的方式写每一类背包问题的,也非常清楚它们的实现中细微的不同,那么在遇到混合三种背包问题的题目时,一定能很快想到上面简洁的解法,对吗?

4.4小结

有人说,困难的题目都是由简单的题目叠加而来的。这句话是否公理暂且存之不论,但它在本讲中已经得到了充分的体现。本来01背包、完全背包、多重背包都不是什么难题,但将它们简单地组合起来以后就得到了这样一道一定能吓倒不少人的题目。但只要基础扎实,领会三种基本背包问题的思想,就可以做到把困难的题目拆分成简单的题目来解决。

8

5

5.1

二维费用的背包问题

问题

二维费用的背包问题是指:对于每件物品,具有两种不同的空间耗费,选择这件物品必须同时付出这两种代价。对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。

设这两种代价分别为代价一和代价二,第i件物品所需的两种代价分别为Ci和Di。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为Wi。

5.2算法

费用加了一维,只需状态也加一维即可。设F[i,v,u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。状态转移方程就是:

F[i,v,u]=max{F[i?1,v,u],F[i?1,v?Ci,u?Di]+Wi}

如前述优化空间复杂度的方法,可以只使用二维的数组:当每件物品只可以取一次时变量v和u采用逆序的循环,当物品有如完全背包问题时采用顺序的循环,当物品有如多重背包问题时拆分物品。

这里就不再给出伪代码了,相信有了前面的基础,读者应该能够自己实现出这个问题的程序。

5.3物品总个数的限制

有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取U件物品。这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为1,可以付出的最大件数费用为U。换句话说,设F[v,u]表示付出费用v、最多选u件时可得到的最大价值,则根据物品的类型(01、完全、多重)用不同的方法循环更新,最后在f[0...V,0...U]范围内寻找答案。

5.4复整数域上的背包问题

另一种看待二维背包问题的思路是:将它看待成复整数域上的背包问题。也就是说,背包的容量以及每件物品的费用都是一个复整数。而常见的一维背包问题则是自然数域上的背包问题。所以说,一维背包的种种思想方法,往往可以应用于二位背包问题的求解中,因为只是数域扩大了而已。

作为这种思想的练习,你可以尝试将后文中提到的“子集和问题”扩展到二维,并试图用同样的复杂度解决。

5.5小结

当发现由熟悉的动态规划题目变形得来的题目时,在原来的状态中加一维以满足新的限制是一种比较通用的方法。希望你能从本讲中初步体会到这种方法。

9

6

6.1

分组的背包问题

问题

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

6.2算法

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

F[k,v]=max{F[k?1,v],F[k?1,v?Ci]+Wi|itemi∈groupk}使用一维数组的伪代码如下:

fork=1toKforv=Vto0

foritemiingroupk

F[v]=max{F[v],F[v?Ci]+Wi}

这里三层循环的顺序保证了每一组内的物品最多只有一个会被添加到背包中。

另外,显然可以对每组内的物品应用2.3中的优化。

6.3小结

分组的背包问题将彼此互斥的若干物品称为一个组,这建立了一个很好的模型。不少背包问题的变形都可以转化为分组的背包问题(例如7),由分组的背包问题进一步可定义“泛化物品”的概念,十分有利于解题。

7

7.1

有依赖的背包问题

简化的问题

这种背包问题的物品间存在某种“依赖”的关系。也就是说,物品i依赖于物品j,表示若选物品i,则必须选物品j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。

7.2算法

这个问题由NOIP2006题目中金明的预算方案一题扩展而来。遵从该题的提法,将不依赖于别的物品的物品称为“主件”,依赖于某主件的物品称为“附件”。由这个问题的简化条件可知所有的物品由若干主件和依赖于每个主件的一个附件集合组成。

按照背包问题的一般思路,仅考虑一个主件和它的附件集合。可是,可用的策略非常多,包括:一个也不选,仅选择主件,选择主件后再选择一个附件,选择主件后再选择两个附件……无法用状态转移方程来表示如此多的策略。事实上,设有n个附件,则策略有2n+1个,为指数级。

10

背包九讲 2.0

你会发现,这个伪代码与01背包问题的伪代码只有v的循环次序不同而已。为什么这个算法就可行呢?首先想想为什么01背包中要按照v递减的次序来循环。让v递减是为了保证第i次循环中的状态F[i,v]是由状态F[i?1,v?Ci]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果F[i?1,v?Ci]。而
推荐度:
点击下载文档文档为doc格式
30qeu5lwiz10e609mklp
领取福利

微信扫码领取福利

微信扫码分享