以下是学习背包九讲的学习笔记,只有前八讲。原文链接。
01背包问题
Description
有 $n$ 件物品,一个容量为 $m$ 的背包。每个物品有价值 $v$ 和重量 $w$。挑选一些物品放入背包,每个物品只能用一次,求最大价值。
Solution
做法1
设 $f[i][j]$ 为当前到第 $i$ 个物品,已经用 $j$ 容量的最大价值。
转移方程:$f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i])$
以上做法的时间、空间复杂度均为 $O(nm)$。
Code
1 |
|
优化1
时间复杂度难以优化,但可以优化空间复杂度到 $O(m)$。设 $f[i]$ 为表示容量为 $i$ 的背包能够得到的最大价值。
转移方程:$f[i]=max(f[i],f[i-w[i]]+v[i])$
Code
1 |
|
完全背包问题
Description
还是 $01$ 背包问题,但不同的是每个物品可以用无限次。
Solution
做法1
还是设 $f[i][j]$ 为当前到第 $i$ 个物品,已经用 $j$ 容量的最大价值。
每次在循环内枚举 $k$ 作为当前物品使用的次数。直到当前容量再也无法放开为止。
可以看出这种做法的时空复杂度都很高。(因为在 $oj$ 上爆内存,所以下面的代码没有经过测试)
Code
1 |
|
优化1
先不考虑压缩空间,先来优化一下时间复杂度。之前 $i$ 这一层是由 $i-1$ 这一层更新来的,现在我们用 $i$ 这一层来平级地更新自己。会意一下,发现 $k$ 这一层不用循环了。(代码还是没有测试)
Code
1 |
|
优化2
先来明确一下优化后的 $01$ 背包 $j$ 这层倒着循环的意义。比如在 $i$ 固定的情况下,$5$ 这个容量选择了第 $i$ 个物品,而 $7$ 这个容量又选择了 $i$ 和 $5$。可以发现 $i$ 这个物品已经选了两次了,不符合 $01$ 背包的定义。
现在可以重复选择了,$j$ 这一层正着循环就可以了。
优化3
两个物品 $i$ 和 $j$,如果 $w[i]>w[j]$ 且 $v[i]<v[j]$,就把 $i$ 去掉,不用考虑。
Code
1 |
|
多重背包问题
Description
与 $01$ 背包相同,但每个物品可以取 $z$ 次。
Solution
做法1
把每个物品拆成 $z$ 个同样的物品,每个物品可以取一次。但是一旦 $z$ 比较大,这种做法在时空复杂度上就难以通过了。
优化1
把每个物品拆成一些捆,每一捆按照 $2$ 的次方数来拆。最后剩下的物品绑成一捆。
证明:
根据倍增的结论,某个数 $i$ 如果可以表示成 $2^0+2^1+2^2+ … +2^N$(设其为性质 $A$),那么任意一个数 $x$ $(1≤x≤i)$,也可以表示成 $2^a+2^b+2^c+ … +2^M$ $(0≤a<b<c< … <M≤N)$
在此题中,如果物品数量 $z$ 满足性质 $A$,小于等于 $z$ 的任何一个物品都可以表示出来。
否则,设 $p$ 为最大的 $<z$ 而满足性质 $A$ 的数。设要取 $i$ 件物品,分类讨论:
$1.$ $i≤p$,按照上述方法取。
$2.$ $p<i≤z$,先取最后一捆,再按照上述方法取。
QED.
Code
1 |
|
混合三种背包问题
Description
有一些物品只能取 $1$ 件,有一些物品可以取无数件,其他物品可以取 $z_i$ 件。
Solution
首先考虑 $01$ 背包和完全背包的混合。只要判断一下当前物品的种类,看看是要顺序还是逆序循环。加上多重背包:按照上面的方法,拆开物品,将其变成 $01$ 背包(听说可以使用单调队列)。
Code
1 | for(int i = 1; i <= n; i++) |
二维费用背包问题
Description
对于每件物品,这件物品必须同时付出这两种代价,对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。
Solution
设 $f[i][u][v]$ 为前 $i$ 个物品,两种代价分别为 $u$ 和 $v$ 能够得到的最大价值。转移方程:
$f[i][u][v]=max(f[i-1][u-a[i]][v-b[i]]+val[i],f[i-1][u][v])$
可以按照上面优化,去掉 $i$ 这一维。只要注意 $01$ 背包是逆序循环,完全背包是顺序循环就行了。代码以 $01$ 背包为例。
Code
1 |
|
分组背包问题
Description
把 $n$ 个物品分为 $k$ 组,每一组物品最多只能选一个。
Solution
问题变成了,你可以选择本组的一件,或者一件也不选。设 $f[i][j]$ 为前 $k$ 组物品花费 $j$ 能得到的价值。转移方程:
$f[k][j]=max(f[k-1][j],f[k-1][j-w[i]]+val[i])$ ($i$ 属于第 $k$ 组)
可以按照上面的优化方法,把 $f$ 数组变成一维。保证每组只能选一个物品,容量的循环要在每一组物品的循环之外。具体看代码。
Code
1 | for(int i = 1; i <= k; i++) |
有依赖的背包问题
Description
如果选物品 $i$,必须选物品 $j$。
Solution
如果把问题转化为一棵树,若 $j$ 依赖于 $i$,使 $i$ 成为 $j$ 的父亲。连出一虚拟根。只有选择了 $i$,才能选择其子树。
设 $f[i][j]$ 为以 $i$ 为根的子树,已经选择了 $j$ 个节点的最大价值。转移方程:
$f[i][j]=max(f[i][j],f[s][k]+f[i][j-k])$ $(s$ 是 $i$ 的儿子,$0<k<j)$
例题
此题相当于给出 $n$ 个物品,背包容量为 $m$,每个物品的重量为 $1$,价值为 $s_i$,最大化价值。物品之间存在依赖关系。这样就把问题转化成了一般形式,按上面的思路解即可。
Code
1 |
|
泛化背包问题
Description
每个物品没有固定的重量和价值。给它重量 $i$,就会得到价值 $h(i)$。
Solution
设有泛化物品 $h$ 和 $l$,其中 $i(j)$ 表示给 $i$ 这个泛化物品设置 $j$ 费用能得到的价值。若 $f$ 满足 $f(v)=max(h(k)+l(v-k))$,则称 $f=h+l$。
实际上求最终结果的过程,就是不断求泛化物品之和的过程。
v1.5.2