前言
背包(Knapsack)问题是经典的动态规划问题,也很有实际价值。
##背包
$$ f_{i,j}=\begin{cases} 0 & (i=0/j=0) \\ \max(f_{i-1,j},f_{i-1,j-w_i}+v_i) & (i>0,j\ge w_i) \end{cases} $$洛谷 P2871 [USACO07DEC] Charm Bracelet S
AtCoder Educational DP Contest D - Knapsack 1
有$n$个物品和一个总容量为$W$的背包。第$i$件物品的重量是$w_i$,价值是$v_i$。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
依次递增$i$,逐步增加问题规模即可求解。时间、空间复杂度均为$\mathcal O(nW)$。
在本题中,$\mathcal O(nW)$的空间复杂度容易MLE,因此考虑使用数组重复利用或者滚动表的优化。
此时空间复杂度为$\mathcal O(W)$。
一定要牢记这个公式,注意使用时需倒序枚举$j$,防止串连转移。参考代码:
|
|
01背包还有一种简单变形,即求最小剩余空间,此时用$($总空间$-$最大可装空间$)$即可。
|
|
扩展:对付更大的$W$
$$ f_{i,j}=\begin{cases} +\infin & (i=0,j\ne0) \\ 0 & (i\ge 0,j=0) \\ \min(f_{i-1,j},f_{i-1,j-v_i}+w_i) & (i>0,j>0) \end{cases} $$AtCoder Educational DP Contest E - Knapsack 2
本题和普通的01背包完全相同,只是数据范围改为$n\le 100,W\le 10^9,v_i\le 10^3$。
其中,$i=0,j\ne 0$这种情况不存在,所以初始值为$+\infin$。最终答案,即为最大的$j$,使得$f_{n,j}\le W$,更新状态时可同时记录这个答案。
这种算法的时间和空间都可以优化:
- 时间:对于每个$i$,循环迭代$j$时只需到$v_1+v_2+\dots+v_i$即可,因为当前的总价值不可能超过这个值;
- 空间:用与前面完全相同的方法,压缩掉第一维空间,变成$f_j=\min(f_j,f_{j-v_i}+w_i)$(注意要倒序枚举$j$)
运用了两种优化的代码如下:
|
|
完全背包
$$ \begin{aligned} f_{i,j}=\max_{k=0}^{+\infin}\{f_{i-1,j-k\times w_i}\}+v_i\times k\\ =\max_{k=0}^{\lfloor\frac j{w_i}\rfloor}\{f_{i-1,j-k\times w_i}\}+v_i\times k \end{aligned} $$洛谷 P1616 疯狂的采药
有$n$个物品和一个总容量为$W$的背包。第$i$件物品的重量是$w_i$,价值是$v_i$。每个物品可以使用无限次。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
可以发现,实际上只需要用$f_{i,j}=\max(f_{i-1,j},f_{i,j-w_i}+v_i)$即可,因为此时的$f_{i,j-w_i}$会被$f_{i,j-2w_i}$更新,$f_{i,j-2w_i}$又会被$f_{i,j-3w_i}$更新,以此类推,这样算与前面的公式等效。
实际上,区别就在于一个是$f_{i-1,j-w_i}$,一个是$f_{i,j-w_i}$。所以仍可以使用数组压缩,只需要改变一下循环顺序,是不是很神奇?
long long
别忘了~
|
|
多重背包
洛谷 P1776 宝物筛选
有$n$个物品和一个总容量为$W$的背包。第$i$件物品的重量是$w_i$,价值是$v_i$,最多能选择$m_i$次。我们要选择一些物品,使这些物品的重量总和不超过背包容量,且价值总和最大。$n\le100,\sum m_i\le10^5,W\le 4\times10^4$。
很容易想到,可以转换成每件物品都被拆分成$m_i$个只能选一次的物品,即$N=\sum m_i$的01背包。很明显,这样做的时间复杂度是$\mathcal O(W\sum m_i)$,会TLE。因此,我们可以优化拆分的方法,将$m$转化为$x+\sum\limits_{j=0}^i 2^j$的形式,举几个栗子:
- $5=(1+2)+2$,其中$i=1,x=2$;
- $16=(1+2+4+8)+1$,其中$i=3,x=1$;
- $31=(1+2+4+8+16)$,其中$i=4,x=0$。
这种方法的正确性这里就不详细说明了,主要依赖于二进制的拼凑。容易发现,数字$m$按这种拆分的方法会被拆分为$\lceil\log_2m\rceil$个数字的和,因此总时间复杂度为$\mathcal O(W\sum\limits_{i=1}^n\log m_i)$,可以通过此题。
还有一种单调队列/单调栈优化,同样针对多重背包问题,时间复杂度为$\mathcal O(nW)$,有时不一定优于二进制优化,这里就不多说了。下面给出二进制优化的参考程序。
|
|
混合背包
没错,就是前三种放在一起的背包。不用的物品有不同的种类。比如洛谷 P1833 樱花,就是混合背包。不过,也不要慌,直接用个分支判断,比如:
|
|
实际上也不一定要这样,可以全部统一成混合背包:
- 对于01背包,可选数目$k_i=1$。
- 对于完全背包,可选数目$k_i=\lceil\frac W{w_i}\rceil$。
这里就不给出详细代码,有兴趣的读者可以自己尝试一下。
总结
让我们来总结一下三种基本背包DP的异同:
项目 | 01背包 | 完全背包 | 多重背包 |
---|---|---|---|
适用场景 | 每件物品只能选择一次 | 每件物品可以无限选择 | 每件物品可以选择的次数有限 |
状态转移方程1 | $\max(f_j,f_{j-w_i}+v_i)$ | $\max(f_j,f_{j-w_i}+v_i)$ | 基本同01背包 |
时间复杂度2 | $\mathcal O(nW)$ | $\mathcal O(nW)$ | $\mathcal O(W\sum\log k_i)$ |
空间复杂度3 | $\mathcal O(W)$ | $\mathcal O(W)$ | $\mathcal O(W)$ |
编码难度 | 低 | 低 | 中 |
创作不易,如果觉得好就请给个三连,谢谢支持!