背包问题:f[v]记录背包容量剩余v时的最大价值。
提炼01背包和完全背包的子过程,用物品(代价c价值w)更新各容量的f[v]。
zeroOnePack(f, c, w):
for v in V..c // 逆序遍历各容量
f[v] = max( f[v], f[v-c]+w )
completePack(f, c, w):
for v in c..V // 顺序遍历各容量
f[v] = max( f[v], f[v-c]+w )
不管正序还是逆序,右边的f[v]肯定是f[i-1][v]。01背包和完全背包都在考虑放不放第i个物品,01背包的f[v-c]是旧状态f[i-1][v-c],逆序保证v维左边是旧状态。完全背包在考虑第i个物品时不管这个物品放没放入过,所以完全背包的f[v-c]是新状态f[i][v-c],正序保证v维左边是新状态。
初始值:初始的f[]就是在没有放入任何物品时背包的合法状态。如果要求“恰好“装满背包,则f[0]=0其他f[1..V]=-∞;如果没要求恰好装满,则f[0..V]=0。
多重背包:或者某物品多得可当作完全背包问题,或者可把原物品分堆成1,2,4...等重量翻倍的物品 和 剩余重量>0的一个物品,这样就变成多个01背包问题。
multiplePack(f, c, w, m):
if (cm >= V)
completePack(f, c, w)
return
k = 1
while (k < m) // <号保证剩余重量>0
zeroOnePack(f, kc, kw);
m -= k;
k *= 2;
zeroOnePack(f, mc, mw);
二维费用的背包问题:像背包空间v作费用那样,多加一维u作费用,就变成 f[v,u]。对于u同样地,01背包逆序循环,完全背包正序循环。比如限制物品总个数,相当于多了一维件数费用,每个物品的件数费用为1。
分组的背包问题:分为K组,每组最多选一件。设f[k, v]表示前k组物品、容量剩余v时的最大价值,第k组的策略是不选或组中选一件,f[k,v] = max{ f[k-1,v], f[k-1, v-ci] + wi | item i ∈ group k }。代码就是在zeroOnePack()最内层加一层循环在组内选择。
参考:背包问题九讲 https://github.com/tianyicui/pack/blob/master/V2.pdf