即用第 i 件的信息更新 f 时,比较不取第 i 件的最大价值 f(i−1,v) 与取第 i 件的最大价值 f(i−1,v−c(i))+w(i) (前提是背包的体积 v 能装下第 i 件物品)
伪代码:空间复杂度 O(NV) ,时间复杂度 O(NV)
1 2 3 4 5 6
f(i,v)=max(f(i-1,v),f(i-1,v-c(i))+w(i)) for i=1 to N for v=c(i) to V f(i,v)=max(f(i-1,v),f(i-1,v-c(i))+w(i)) for v=0 to c(i)-1 f(i,v)=f(i-1,v) // v从0到c(i)-1部分直接从i-1更新过来
空间复杂度优化
省去第一维:设 f(v) 为当下(与遍历到哪件物品有关)背包体积为 v 时的最大价值
思想:注意到 i 只能由 i−1 的状态更新过来,因此只要保证更新时 f 值还是上次更新的值就可以了,第二层循环要从大往小刷,则状态转移所需的 f(v−c(i)) 在第 i 轮还未被更新,仍是第 i−1 轮更新后的值
伪代码:
1 2 3
for i=1 to N for v=V to c(i) f(v)=max(f(v),f(v-c(i))+w(i))
// v[i],w[i],m[i]分别表示第i件物品的体积,价值和可取数量 // V[i],W[i]为二进制拆分后的所有物品的体积和价值,即01背包的初始条件,编号[1,cnt] for (int i = 1; i <= n; i++) { cin >> v[i] >> w[i] >> m[i]; int x = 1; // 二进制系数2^k while (x <= m[i]) { V[++cnt] = v[i] * x; W[cnt] = w[i] * x; m[i] -= x; x <<= 1; } if (m[i] > 0) // m-2^{k+1}+1 { V[++cnt] = v[i] * m[i]; W[cnt] = w[i] * m[i]; } } ZeroOnePack(f, V, W);
为方便后面描述,我们将上述代码的逻辑过程 MultiplePack(f,c,w)
混合背包
设定:共 n 件物品,第 i 件物品的体积为 c(i) ,价值为 w(i) ,每件物品有 m(i) 个或无限个
分析:不同类型的物品按不同类型处理即可
伪代码:
1 2 3 4 5 6 7
for i=1 to N if 第i件是01背包 ZeroOnePack(f,c,w) elseif 第i件是完全背包 CompletePack(f,c,w) elseif 第i件是多重背包 MultiplePack(f,c,w,m)
二维费用的背包
设定:背包有两个限制条件 V,U (比如体积和重量),共 n 件物品,第 i 件物品的代价(比如体积和重量)为 c(i),d(i) ,价值为 w(i) ,物品的选择可能是01/完全/多重/混合背包
状态设定:f(i,v,u) 为前 i 件放入限制条件为 v,u 的背包中的最大价值,状态转移方程要结合具体背包类型来写(与二维一样)。以01背包为例,f(i,v,u)=max[f(i−1,v,u),f(i−1,v−c(i),u−d(i))+w(i)]
优化:结合具体背包类型来写,与二维一样,只是扩展了维数而已
继续扩展:增加限制条件→ 增加维数
分组背包
设定:共 n 件物品,第 i 件物品的体积为 c(i) ,价值为 w(i) ,每件物品只有 1 个。这些物品被划分成 K 组,组中物品最多选一件。
分析:显然组内是一个01背包,设 f(k,v) 表示第 k 组用体积为 v 的背包,能获得的最大价值(即普通01背包加了一维第几组)。这样每一组便又是一个01背包,代码可以写在一起(反正都是从大到小遍历背包体积)
伪代码:
1 2 3 4 5 6 7
for k=1 to K for v=V to 0// 组间01背包 for item i in k // 组内01 if(v>=c(i)) f(v)=max(f(v),f(v-c(i))+w(i)) // 此时用的是k-1组的价值更新 // 对于同一个v,第k组内的物品至多只有一个被选上(因为一直在取max)
有依赖的背包
设定
共 n 件物品,第 i 件物品的体积为 c(i) ,价值为 w(i)
物品间存在特定依赖关系:如果选择物品 x ,必须选择物品 y ;反之如果选择物品 y ,不一定选择物品 x。将 x 称主件,y 称附件。