【笔记】《背包九讲》阅读笔记
01背包:
这是最基本的背包问题,每个物品最多只能放一次。
题目:有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
状态转移方程 初始版:
for i=1..N for v=V..0 f[v]=max{f[v],f[v-c[i]]+w[i]};
优化加函数化:ZeroOnePack,表示处理一件01背包中的物品,
procedure ZeroOnePack(cost,weight) for v=V..cost f[v]=max{f[v],f[v-cost]+weight}
此后01背包如下:
for i=1..N ZeroOnePack(c[i],w[i]);
注意事宜:
【是否完全装满背包】:初始化操作
1.要求“恰好装满背包”时的最优解,
除了f[0]为0,其它f[1..V]均设为-∞(这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解)
2.不要求必须把背包装满。
将f[0..V]全部设为0。
【常数优化】
for i=1..n bound=max{V-sum{w[i..n]},c[i]} for v=V..bound
完全背包:
第二个基本的背包问题模型,每种物品可以放无限多次。
复杂度:O(VN)
for i=1..N for v=0..V f[v]=max{f[v],f[v-cost]+weight}
多重背包:
每种物品有一个固定的次数上限。
复杂度:O(V*Σlog n[i])
procedure MultiplePack(cost,weight,amount) if cost*amount>=V //转化为完全背包 CompletePack(cost,weight) return integer k=1 while k<amount //拆分成log(amount)件01背包 ZeroOnePack(k*cost,k*weight) amount=amount-k k=k*2 ZeroOnePack(amount*cost,amount*weight)
混合背包:
将前面三种简单的问题叠加成较复杂的问题。
for i=1..N if 第i件物品属于01背包 ZeroOnePack(c[i],w[i]) else if 第i件物品属于完全背包 CompletePack(c[i],w[i]) else if 第i件物品属于多重背包 MultiplePack(c[i],w[i],n[i])
二维费用的背包问题:
分组的背包问题:
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。
这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。
求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
for 所有的组k for v=V..0 for 所有的i属于组k f[v]=max{f[v],f[v-c[i]]+w[i]}
有依赖的背包问题:
i依赖于j,表示若选物品i,则必须选物品j
1、分为主/附件:
对主件i的“附件集合”先进行一次01背包,得到费用依次为0..V-c[i]所有这些值时相应的最大价值f'[0..V-c[i]]。
那么这个主件及它的附件集合相当于V-c[i]+1个物品的物品组,其中费用为c[i]+k的物品的价值为f'[k]+w[i]。
2、更一般的问题:
依赖关系以图论中“森林”的形式给出,限制只是每个物品最多只依赖于一个物品(只有一个主件)且不出现循环依赖。
事实上,这是一种树形DP,其特点是每个父节点都需要对它的各个儿子的属性进行一次DP以求得自己的相关属性。
泛化物品:
来自背包问题(背包九讲) - Code-dream - 博客园 (cnblogs.com)
考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念。
更严格的定义之。在背包容量为V的背包问题中,泛化物品是一个定义域为0..V中的整数的函数h,当分配给它的费用为v时,能得到的价值就是h(v)。
这个定义有一点点抽象,另一种理解是一个泛化物品就是一个数组h[0..V],给它费用v,可得到价值h[V]。
一个费用为c价值为w的物品,如果它是01背包中的物品,那么把它看成泛化物品,它就是除了h(c)=w其它函数值都为0的一个函数。如果它是完全背包中的物品,那么它可以看成这样一个函数,仅当v被c整除时有h(v)=v/c*w,其它函数值均为0。如果它是多重背包中重复次数最多为n的物品,那么它对应的泛化物品的函数有h(v)=v/c*w仅当v被c整除且v/c<=n,其它情况函数值均为0。
一个物品组可以看作一个泛化物品h。对于一个0..V中的v,若物品组中不存在费用为v的的物品,则h(v)=0,否则h(v)为所有费用为v的物品的最大价值。P07中每个主件及其附件集合等价于一个物品组,自然也可看作一个泛化物品。
泛化物品的定义表明:在一个背包问题中,若将两个泛化物品代以它们的和,不影响问题的答案。
事实上,对于其中的物品都是泛化物品的背包问题,求它的答案的过程也就是求所有这些泛化物品之和的过程。设此和为s,则答案就是s[0..V]中的最大值。
背包问题问法的变化
【输出方案】
可以参照一般动态规划问题输出方案的方法:记录下每个状态的最优值是由状态转移方程的哪一项推出来的
再用一个数组g[i][v],设g[i][v]=0表示推出f[i][v]的值时是采用了方程的前一项(也即f[i][v]=f[i-1] [v]),g[i][v]表示采用了方程的后一项
i=N v=V while(i>0) if(g[i][v]==0) print "未选第i项物品" else if(g[i][v]==1) print "选了第i项物品" v=v-c[i]
【输出字典序最小的方案】
一般而言,求一个字典序最小的最优方案,只需要在转移时注意策略。
首先,子问题的定义要略改一些。我们注意到,如果存在一个选了物品1的最优方案, 那么答案一定包含物品1,原问题转化为一个背包容量为v-c[1],物品为2..N的子问题。反之,如果答案不包含物品1,则转化成背包容量仍为V,物品为2..N的子问题。
不管答案怎样,子问题的物品都是以i..N而非前所述的1..i的形式来定义的,所以状态的定义和转移方程都需要改一下。
但也许更简易的方法是先把物品逆序排列一下。在这种情况下,可以按照前面经典的状态转移方程来求值,只是输出方案的时候要注意:从N到1输入时,如果f[i][v]==f[i-1][i-v]及f[i][v]==f[i-1][f-c[i]]+w[i]同时成立,应该按照后者(即选择了物品i)来输出方案。
【求方案总数】
一般只需将状态转移方程中的max改成sum即可。例如若每件物品均是完全背包中的物品,转移方程即为
f[i][v]=sum{f[i-1][v],f[i][v-c[i]]}
【求第k优解】
第K优解则比求最优解的复杂度上多一个系数K。
其基本思想是将每个状态都表示成有序队列,将状态转移方程中的max/min转化成有序队列的合并。这里仍然以01背包为例讲解一下。
首先看01背包求最优解的状态转移方程:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
。如果要求第K优解,那么状态f[i][v]就应该是一个大小为K的数组f[i][v][1..K]。其中f[i][v][k]表示前i个物品、背包大小为 v时,第k优解的值。“显然f[i][v][1..K]这K个数是由大到小排列的,所以我们把它认为是一个有序队列。
总的复杂度是O(VNK)。
另外还要注意题目对于“第K优解”的定义,将策略不同但权值相同的两个方案是看作同一个解还是不同的解。如果是前者,则维护有序队列时要保证队列里的数没有重复的。
背包问题的搜索解法
首先,可以从数据范围中得到命题人意图的线索。如果一个背包问题可以用DP解,V一定不能很大,否则O(VN)的算法无法承受,而一般的搜索解法都是仅与N有关,与V无关的。所以,V很大时(例如上百万),命题人的意图就应该是考察搜索。另一方面,N较大时(例如上百),命题人的意图就很有可能是考 察动态规划了。