收藏 分享(赏)

背包问题_九讲.doc

上传人:hwpkd79526 文档编号:7402241 上传时间:2019-05-16 格式:DOC 页数:25 大小:113KB
下载 相关 举报
背包问题_九讲.doc_第1页
第1页 / 共25页
背包问题_九讲.doc_第2页
第2页 / 共25页
背包问题_九讲.doc_第3页
第3页 / 共25页
背包问题_九讲.doc_第4页
第4页 / 共25页
背包问题_九讲.doc_第5页
第5页 / 共25页
点击查看更多>>
资源描述

1、极好很全背包问题九讲目录 第一讲 01 背包问题 第二讲 完全背包问题 第三讲 多 重背包问题 第四讲 混 合三种背包问题 第五讲 二维 费用的背包问题 第六讲 分组 的背包问题 第七讲 有依赖的背包问题 第八讲 泛化物品 第九讲 背包问题问法的变化 附:U SACO 中的 背包问题 前言本篇文章是我(dd_engi)正在进行中的一个雄心勃勃的写作计划的一部分,这个计划的内容是写作一份较为完善的 NOIP 难度的动态规划总结,名为解动态规划题的基本思考方式。现在你看到的是这个写作计划最先发布的一部分。背包问题是一个经典的动态规划模型。它既简单形象容易理解,又在某种程度上能够揭示动态规划的本质,

2、故不少教材都把它作为动态规划部分的第一道例题,我也将它放在我的写作计划的第一部分。读本文最重要的是思考。因为我的语言和写作方式向来不以易于理解为长,思路也偶有跳跃的地方,后面更有需要大量思考才能理解的比较抽象的内容。更重要的是:不大量思考,绝对不可能学好动态规划这一信息学奥赛中最精致的部分。你现在看到的是本文的 1.0 正式版。我会长期维护这份文本,把大家的意见和建议融入其中,也会不断加入我在 OI 学习以及将来可能的 ACM-ICPC 的征程中得到的新的心得。但目前本文还没有一个固定的发布页面,想了解本文是否有更新版本发布,可以在 OIBH 论坛中以“背包问题九讲”为关键字搜索贴子,每次比较

3、重大的版本更新都会在这里发贴公布。目录第一讲 01 背包 问题这是最基本的背包问题,每个物品最多只能放一次。第二讲 完全背包问题极好很全第二个基本的背包问题模型,每种物品可以放无限多次。第三讲 多重背包问题每种物品有一个固定的次数上限。第四讲 混合三种背包问题将前面三种简单的问题叠加成较复杂的问题。第五讲 二维费用的背包问题一个简单的常见扩展。第六讲 分组的背包 问题一种题目类型,也是一个有用的模型。后两节的基础。第七讲 有依赖的背包问题另一种给物品的选取加上限制的方法。第八讲 泛化物品我自己关于背包问题的思考成果,有一点抽象。第九讲 背包问题问法的变化试图触类旁通、举一反三。附:USACO

4、中的背包问题给出 USACO Training 上可供练习的背包问题列表,及简单的解答。联系方式如果有任何意见和建议,特别是文章的错误和不足,或者希望为文章添加新的材料,可以通过 http:/ 阿坦 jason911 极好很全 donglixp 他们每人都最先指出了本文第一个 beta 版中的某个并非无关紧要的错误。谢谢你们如此仔细地阅读拙作并弥补我的疏漏。感谢 XiaQ,它针对本文的第一个 beta 版发表了用词严厉的六条建议,虽然我只认同并采纳了其中的两条。在所有读者几乎一边倒的赞扬将我包围的当时,你的贴子是我的一剂清醒剂,让我能清醒起来并用更严厉的眼光审视自己的作品。当然,还有用各种方式

5、对我表示鼓励和支持的几乎无法计数的同学。不管是当面赞扬,或是在论坛上回复我的贴子,不管是发来热情洋溢的邮件,或是在即时聊天的窗口里竖起大拇指,你们的鼓励和支持是支撑我的写作计划的强大动力,也鞭策着我不断提高自身水平,谢谢你们!最后,感谢 Emacs 这一世界最强大的编辑器的所有贡献者,感谢它的插件 EmacsMuse 的开发者们,本文的所有编辑工作都借助这两个卓越的自由软件完成。谢谢你们自由软件社群为社会提供了如此有生产力的工具。我深深钦佩你们身上体现出的自由软件的精神,没有你们的感召,我不能完成本文。在你们的影响下,采用自由文档的方式发布本文档,也是我对自由社会事业的微薄努力。极好很全P01

6、: 01 背包问题题目有 N 件物品和一个容量为 V 的背包。第 i 件物品的费用是 ci,价值是 wi。求解将哪些物品装入背包可使价值总和最大。基本思路这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。用子问题定义状态:即 fiv表示前 i 件物品恰放入一个容量为 v 的背包可以获得的最大价值。则其状态转移方程便是:fiv=maxfi-1v,fi-1v-ci+wi这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前 i 件物品放入容量为 v 的背包中”这个子问题,若只考虑第 i 件物品的策略(放或不放),那么就可以转化为一个

7、只牵扯前 i-1 件物品的问题。如果不放第 i 件物品,那么问题就转化为“前 i-1件物品放入容量为 v 的背包中”,价值为 fi-1v;如果放第 i 件物品,那么问题就转化为“前 i-1 件物品放入剩下的容量为 v-ci的背包中”,此时能获得的最大价值就是 fi-1v-ci再加上通过放入第 i 件物品获得的价值wi。优化空间复杂度以上方法的时间和空间复杂度均为 O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到 O(V)。先考虑上面讲的基本思路如何实现,肯定是有一个主循环 i=1N,每次算出来二维数组 fi0V的所有值。那么,如果只用一个数组 f0V,能不能保证第 i

8、 次循环结束后 fv中表示的就是我们定义的状态 fiv呢?fiv是由 fi-1v和 fi-1v-ci两个子问题递推而来,能否保证在推 fiv时(也即在第 i 次主循环中推 fv时)能够得到 fi-1v和 fi-1v-ci的值呢?事实上,这要求在每次主循环中我们以 v=V0 的顺序推 fv,这样才能保证推 fv时 fv-ci保存的是状态 fi-1v-ci的值。伪代码如下:for i=1Nfor v=V0fv=maxfv,fv-ci+wi;其中的 fv=maxfv,fv-ci一句恰就相当于我们的转移方程 fiv=maxfi-1v,fi-1v-ci,因为现在的 fv-ci就相当于原来的极好很全fi-

9、1v-ci。如果将 v 的循环顺序从上面的逆序改成顺序的话,那么则成了 fiv由 fiv-ci推知,与本题意不符,但它却是另一个重要的背包问题 P02 最简捷的解决方案,故学习只用一维数组解 01 背包问题是十分必要的。事实上,使用一维数组解 01 背包的程序在后面会被多次用到,所以这里抽象出一个处理一件 01 背包中的物品过程,以后的代码中直接调用不加说明。过程 ZeroOnePack,表示处理一件 01 背包中的物品,两个参数 cost、weight分别表明这件物品的费用和价值。procedure ZeroOnePack(cost,weight)for v=Vcostfv=maxfv,fv

10、-cost+weight注意这个过程里的处理与前面给出的伪代码有所不同。前面的示例程序写成v=V0 是为了在程序中体现每个状态都按照方程求解了,避免不必要的思维复杂度。而这里既然已经抽象成看作黑箱的过程了,就可以加入优化。费用为cost 的物品不会影响状态 f0cost-1,这是显然的。有了这个过程以后,01 背包问题的伪代码就可以这样写:for i=1NZeroOnePack(ci,wi);初始化的细节问题我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现方法是在初始化的时候有所

11、不同。如果是第一种问法,要求恰好装满背包,那么在初始化时除了 f0为 0 其它f1V均设为-,这样就可以保证最终得到的 fN是一种恰好装满背包的最优解。如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f0V全部设为 0。为什么呢?可以这样理解:初始化的 f 数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为 0 的背包可能被价值为 0 的 nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为 0,所以

12、初始时状态的值也就全部为 0 了。极好很全这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。小结01 背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最基本思想,另外,别的类型的背包问题往往也可以转换成 01 背包问题求解。故一定要仔细体会上面基本思路的得出方法,状态转移方程的意义,以及最后怎样优化的空间复杂度。首页极好很全P02: 完全背包问题题目有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。第 i 种物品的费用是 ci,价值是 wi。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。基本思路

13、这个问题非常类似于 01 背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取 0 件、取1 件、取 2 件等很多种。如果仍然按照解 01 背包时的思路,令 fiv表示前 i 种物品恰放入一个容量为 v 的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:fiv=maxfi-1v-k*ci+k*wi|0=wj,则将物品 j 去掉,不用考虑。这个优化的正确性显然:任何情况下都可将价值小费用高得 j 换成物美价廉的 i,得到至少不会更差的方案。对于随机生成的数据,这个方法往往会大大减少物品的件数,从而加快速度。然而这个并

14、不能改善最坏情况的复杂度,因为有可能特别设计的数据可以一件物品也去不掉。这个优化可以简单的 O(N2)地实现,一般都可以承受。另外,针对背包问题而言,比较不错的一种方法是:首先将费用大于 V 的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的是哪个,可以 O(V+N)地完成这个优化。这个不太重要的过程就不给出伪代码了,希望你能独立思考写出伪代码或程序。转化为 01 背包问题求解极好很全既然 01 背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为 01 背包问题来解。最简单的想法是,考虑到第 i 种物品最多选 V/ci件,于是可以把第 i 种物品转化为 V/

15、ci件费用及价值均不变的物品,然后求解这个 01 背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为 01 背包问题的思路:将一种物品拆成多件物品。更高效的转化方法是:把第 i 种物品拆成费用为 ci*2k、价值为 wi*2k的若干件物品,其中 k 满足 ci*2k0 的最大整数。例如,如果 ni为 13,就将这种物品分成系数分别为 1,2,4,6 的四件物品。分成的这几件物品的系数和为 ni,表明不可能取多于 ni件的第 i 种物品。另外这种方法也能保证对于 0ni间的每一个整数,均可以用若干个系数的和表示,这个证明可以分 02k-1 和 2kni两段来分别

16、讨论得出,并不难,希望你自己思考尝试一下。这样就将第 i 种物品分成了 O(log ni)种物品,将原问题转化为了复杂度为O(V*log ni)的 01 背包问题,是很大的改进。极好很全下面给出 O(log amount)时间处理一件多重背包中物品的过程,其中 amount 表示物品的数量:procedure MultiplePack(cost,weight,amount)if cost*amount=VCompletePack(cost,weight)returninteger k=1while k0)if(giv=0)print “未选第 i 项物品“else if(giv=1)print

17、 “选了第 i 项物品“v=v-ci另外,采用方程的前一项或后一项也可以在输出方案的过程中根据 fiv的值实时地求出来,也即不须纪录 g 数组,将上述代码中的 giv=0 改成 fiv=fi-1v,giv=1 改成 fiv=fi-1v-ci+wi也可。输出字典序最小的最优方案这里“字典序最小”的意思是 1N 号物品的选择方案排列出来以后字典序最小。以输出 01 背包最小字典序的方案为例。极好很全一般而言,求一个字典序最小的最优方案,只需要在转移时注意策略。首先,子问题的定义要略改一些。我们注意到,如果存在一个选了物品 1 的最优方案,那么答案一定包含物品 1,原问题转化为一个背包容量为 v-c

18、1,物品为 2N的子问题。反之,如果答案不包含物品 1,则转化成背包容量仍为 V,物品为2N 的子问题。不管答案怎样,子问题的物品都是以 iN 而非前所述的 1i的形式来定义的,所以状态的定义和转移方程都需要改一下。但也许更简易的方法是先把物品逆序排列一下,以下按物品已被逆序排列来叙述。在这种情况下,可以按照前面经典的状态转移方程来求值,只是输出方案的时候要注意:从 N 到 1 输入时,如果 fiv=fi-v及 fiv=fi-1f-ci+wi同时成立,应该按照后者(即选择了物品 i)来输出方案。求方案总数对于一个给定了背包容量、物品费用、物品间相互关系(分组、依赖等)的背包问题,除了再给定每个

19、物品的价值后求可得到的最大价值外,还可以得到装满背包或将背包装至某一指定容量的方案总数。对于这类改变问法的问题,一般只需将状态转移方程中的 max 改成 sum 即可。例如若每件物品均是完全背包中的物品,转移方程即为fiv=sumfi-1v,fiv-ci初始条件 f00=1。事实上,这样做可行的原因在于状态转移方程已经考察了所有可能的背包组成方案。最优方案的总数这里的最优方案是指物品总价值最大的方案。以 01 背包为例。结合求最大总价值和方案总数两个问题的思路,最优方案的总数可以这样求:fiv意义同前述,giv表示这个子问题的最优方案的总数,则在求 fiv的同时求 giv的伪代码如下:for

20、i=1Nfor v=0Vfiv=maxfi-1v,fi-1v-ci+wigiv=0if(fiv=fi-1v)inc(giv,gi-1vif(fiv=fi-1v-ci+wi)inc(giv,gi-1v-ci)如果你是第一次看到这样的问题,请仔细体会上面的伪代码。极好很全求次优解、第 K 优解对于求次优解、第 K 优解类的问题,如果相应的最优解问题能写出状态转移方程、用动态规划解决,那么求次优解往往可以相同的复杂度解决,第 K 优解则比求最优解的复杂度上多一个系数 K。其基本思想是将每个状态都表示成有序队列,将状态转移方程中的 max/min 转化成有序队列的合并。这里仍然以 01 背包为例讲解一

21、下。首先看 01 背包求最优解的状态转移方程:fiv=maxfi-1v,fi-1v-ci+wi。如果要求第 K 优解,那么状态 fiv就应该是一个大小为 K 的数组 fiv1K。其中 fivk表示前 i 个物品、背包大小为 v 时,第k 优解的值。“fiv是一个大小为 K 的数组”这一句,熟悉 C 语言的同学可能比较好理解,或者也可以简单地理解为在原来的方程中加了一维。显然 fiv1K这 K 个数是由大到小排列的,所以我们把它认为是一个有序队列。然后原方程就可以解释为:fiv这个有序队列是由 fi-1v和 fi-1v-ci+wi这两个有序队列合并得到的。有序队列 fi-1v即 fi-1v1K,

22、fi-1v-ci+wi则理解为在 fi-1v-ci1K的每个数上加上 wi后得到的有序队列。合并这两个有序队列并将结果(的前 K 项)储存到 fiv1K中的复杂度是 O(K)。最后的答案是 fNVK。总的复杂度是 O(NVK)。为什么这个方法正确呢?实际上,一个正确的状态转移方程的求解过程遍历了所有可用的策略,也就覆盖了问题的所有方案。只不过由于是求最优解,所以其它在任何一个策略上达不到最优的方案都被忽略了。如果把每个状态表示成一个大小为 K 的数组,并在这个数组中有序的保存该状态可取到的前 K 个最优值。那么,对于任两个状态的 max 运算等价于两个由大到小的有序队列的合并。另外还要注意题目

23、对于“第 K 优解”的定义,将策略不同但权值相同的两个方案是看作同一个解还是不同的解。如果是前者,则维护有序队列时要保证队列里的数没有重复的。小结显然,这里不可能穷尽背包类动态规划问题所有的问法。甚至还存在一类将背包类动态规划问题与其它领域(例如数论、图论)结合起来的问题,在这篇论背包问题的专文中也不会论及。但只要深刻领会前述所有类别的背包问题的思路和状态转移方程,遇到其它的变形问法,只要题目难度还属于 NOIP,应该也不难想出算法。触类旁通、举一反三,应该也是一个 OIer 应有的品质吧。首页极好很全附:USACO 中的背包问题USACO 是 USA Computing Olympiad 的

24、简称,它组织了很多面向全球的计算机竞赛活动。USACO Trainng 是一个很适合初学者的题库,我认为它的特色是题目质量高,循序渐进,还配有不错的课文和题目分析。其中关于背包问题的那篇课文 (TEXT Knapsack Problems) 也值得一看。另外, USACO Contest 是 USACO 常年组织的面向全球的竞赛系列,在此也推荐NOIP 选手参加。我整理了 USACO Training 中涉及背包问题的题目,应该可以作为不错的习题。其中标加号的是我比较推荐的,标叹号的是我认为对 NOIP 选手比较有挑战性的。题目列表 Inflate (+) (基本 01 背包) Stamps

25、(+)(!) (对初学者有一定挑战性) Money Nuggets Subsets Rockers (+) (另一类有趣的“二维”背包问题) Milk4 (!) (很怪的背包问题问法,较难用纯 DP 求解) 题目简解以下文字来自我所撰的USACO 心得一文,该文的完整版本,包括我的程序,可在 DD 的 USACO 征程中找到。Inflate 是加权 01 背包问题,也就是说:每种物品只有一件,只可以选择放或者不放;而且每种物品有对应的权值,目标是使总权值最大或最小。它最朴素的状态转移方程是:fki = maxfk-1i , fk-1i-vk+wk。fki表示前 k 件物品花费代价 i 可以得到

26、的最大权值。vk和 wk分别是第 k 件物品的花费和权值。可以看到, fk的求解过程就是使用第 k 件物品对 fk-1进行更新的过程。那么事实上就不用使用二维数组,只需要定义 fi,然后对于每件物品 k,顺序地检查 fi与 fi-vk+wk的大小,如果后者更大,就对前者进行更新。这是背包问题中典型的优化方法。题目 stamps 中,每种物品的使用量没有直接限制,但使用物品的总量有限制。求第一个不能用这有限个物品组成的背包的大小。(可以这样等价地认为)设fki 表示前 k 件物品组成大小为 i 的背包, 最少需要物品的数量。则极好很全fki= minfk-1i,fk-1i-j*sk+j,其中 j

27、 是选择使用第 k 件物品的数目,这个方程运用时可以用和上面一样的方法处理成一维的。求解时先设置一个粗糙的循环上限,即最大的物品乘最多物品数。Money 是多重背包问题。也就是每个物品可以使用无限多次。要求解的是构成一种背包的不同方案总数。基本上就是把一般的多重背包的方程中的 min 改成sum 就行了。Nuggets 的模型也是多重背包。要求求解所给的物品不能恰好放入的背包大小的最大值(可能不存在)。只需要根据“若 i、j 互质,则关于 x、y 的不定方程 i*x+y*j=n 必有正整数解,其中 ni*j”这一定理得出一个循环的上限。 Subsets 子集和问题相当于物品大小是前 N 个自然

28、数时求大小为 N*(N+1)/4 的 01 背包的方案数。Rockers 可以利用求解背包问题的思想设计解法。我的状态转移方程如下: fijt=maxfijt-1 , fi-1jt , fi-1jt-timei+1 , fi-1j-1T+(t=timei)。其中 fijt表示前 i 首歌用 j 张完整的盘和一张录了 t 分钟的盘可以放入的最多歌数,T 是一张光盘的最大容量,t=timei是一个 bool 值转换成 int 取值为 0 或 1。但我后来发现我当时设计的状态和方程效率有点低,如果换成这样:fij=(a,b)表示前 i 首歌中选了 j 首需要用到 a 张完整的光盘以及一张录了 b 分

29、钟的光盘,会将时空复杂度都大大降低。这种将状态的值设为二维的方法值得注意。Milk4 是这些类背包问题中难度最大的一道了。很多人无法做到将它用纯 DP 方法求解,而是用迭代加深搜索枚举使用的桶,将其转换成多重背包问题再DP。由于 USACO 的数据弱,迭代加深的深度很小,这样也可以 AC,但我们还是可以用纯 DP 方法将它完美解决的。设 fk为称量出 k 单位牛奶需要的最少的桶数。那么可以用类似多重背包的方法对 f 数组反复更新以求得最小值。然而困难在于如何输出字典序最小的方案。我们可以对每个 i 记录 pre_fi和pre_vi。表示得到 i 单位牛奶的过程是用 pre_fi单位牛奶加上若干

30、个编号为 pre_vi的桶的牛奶。这样就可以一步步求得得到 i 单位牛奶的完整方案。为了使方案的字典序最小,我们在每次找到一个耗费桶数相同的方案时对已储存的方案和新方案进行比较再决定是否更新方案。为了使这种比较快捷,在使用各种大小的桶对 f 数组进行更新时先大后小地进行。USACO 的官方题解正是这一思路。如果认为以上文字比较难理解可以阅读官方程序或我的程序。首页Copyright (c) 2007 Tianyi CuiPermission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation.极好很全整理 by stntwm

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报