零、一些记号与约定
物品种类个数:\(n\)。
背包最大容量:\(m\),无特殊声明外非负。
每种物品的体积:\(v_i\),无特殊声明外非负。
每种物品的价值:\(w_i\)。
每种物品的数量:\(c_i\),无特殊声明外 \(c_i=1\)。
物品体积的最大值:\(V=\max_{i}{v_i}\)。
物品价值的最大值:\(W=\max_{i}{w_i}\)。
物品数量的最大值:\(C=\max_{i}{c_i}\)。
一、朴素动态规划
01 背包
\(n\) 个物品,每个物品有体积 \(v_i\) 和价值 \(w_i\),求体积和不超过 \(m\) 的情况下价值和的最大值。
设 \(f_{i,j}\) 表示当前考虑了前 \(i\) 个物品,体积和为 \(j\) 的最大价值和。
转移即为 \(f_{i-1,j}+w_i \rightarrow f_{i,j+v_i}\)。
考虑把第一维去掉,设 \(f_j\) 表示目前体积和为 \(j\) 的最大价值和。
每次新加第 \(i\) 个物品,有转移 \(f_j+w_i \rightarrow f'_{j+v_i}\)。实际上不需要 \(f'\),因为 \(v_i \geq 0\),所以可以直接倒序枚举 \(j\) 转移来避免后效性。
时间复杂度 \(\mathcal O(nm)\),空间复杂度 \(\mathcal O(n+m)\)。
for(int i=1;i<=n;i++) for(int j=m-v[i];j>=0;j--) f[j+v[i]]=max(f[j+v[i]],f[j]+w[i]);
完全背包
\(n\) 个物品,每个物品有体积 \(v_i\) 和价值 \(w_i\),数量有无限个(\(c_i=+\infty\)),求体积和不超过 \(m\) 的情况下价值和的最大值。
跟 \(01\) 背包差不多,只不过转移变为 \(f_j+kw_i \rightarrow f'_{j+kv_i}(k \geq 1)\),直接顺序 dp 即可。
时间复杂度 \(\mathcal O(nm)\),空间复杂度 \(\mathcal O(n+m)\)。
for(int i=1;i<=n;i++) for(int j=0;j<=m-v[i];j--) f[j+v[i]]=max(f[j+v[i]],f[j]+w[i]);
多重背包
\(n\) 个物品,每个物品有体积 \(v_i\) 和价值 \(w_i\),数量有为 \(c_i\),求体积和不超过 \(m\) 的情况下价值和的最大值。
还是考虑 01 背包的 dp 形式,转移变为 \(f_j+kw_i \rightarrow f'_{j+kv_i}(k \in [1,c_i])\)。
直接用单调队列优化 dp,时间复杂度 \(\mathcal O(nm)\),空间复杂度 \(\mathcal O(n+m)\)。
没那么聪明的办法是二进制分组优化,即把每个 \(c_i\) 拆成 \(2^0+2^1+\cdots+2^k+(m-2^{k+1}+1),m-2^{k+1}+1 \in [0,2^{k+1})\),容易证明 \([0,c_i]\) 的所有数都可以被这 \(\log{c_i}+1\) 个数表示出来。
时间复杂度 \(\mathcal O(nm\log C)\),空间复杂度 \(\mathcal O(n\log C+m)\)。