本篇文章仅介绍01背包和完全背包下的背包DP
背包DP的概念:
- 一般给定一个有体积的背包和一些物品以及特定限制,求解最优物品选择方案。在本质上,DP是利用已处理子问题的结论进行下一步递推。但即使这样,搜索毕竟是搜索,该进行的计算是不可省略的,而只能进行已有结论的利用.
- 常见背包DP类型:01背包(每种物品只能选择一次)、完全背包(每种物品可以无限制数量选择)等.
01背包的思想:
- 首先,考虑递推。如果从背包体积为0开始考虑每个物品拿不拿,那大概能推出如是公式:F[v]=max(F[v],F[v-V[i]]+W[i])(v:当前背包体积 F[v]:已装进物品总体积为v时获得的最大利益 V:各个物品分别需要的体积 W:拿各个物品分别的利益 i:当前遍历到的物品序号)
- 但是使用这种方式存在一个问题,可以感性理解一下:如果一个物品的体积只为1,背包总体积为50,而这个物品的贡献为无穷大,则每次都会选择这个物品,总共选择50次,最终的贡献也就是50*INF,也就是说,这个物品总共被选择了50次.
- 那么我们重新考虑遍历方式,既然要保证每种物品只被选择一次,那么可以遍历物品序号,再遍历各个体积(伪代码:for(所有物品) for(体积从1到最大体积) do something;),递推公式不变,应该就可以保证物品不被重复选择了吧?
- 但是很容易想到一种情况,即F[5]=F[3]+W[3] F[4]=F[5]+W[3],在这种情况下,物品3首先在体积为4的情况下被使用;而在体积为5时,又使用了体积为4时的最大价值,在此之上加上物品3的价值。在本质上,这是因为背包的递推是从体积小的已知最大价值推导出更大体积的最大价值。每种物品只应该最多被选择一次,但较小体积和较大体积下都可能选择一个价值很高的物品,这就违反了01背包的规则。
- 那么,怎么才能保证一个物品只被选择一次呢?首先考虑在已有方法上优化。可以考虑在每种体积的情况下都存储已选择的物品,并在每次使用之前首先二分查找以保证没有重复选择物品。很容易发现这种方法复杂度过高。
- 重新考虑遍历方式.
- 如果考虑先遍历物品体积,同一种物品必然被重复选择。外部循环必须是物品种类.
- 只能考虑先遍历物品种类,后反向遍历剩余体积。伪代码:for(物品种类)for(i=体积;i>=0;i--)。由于较大剩余体积只可能利用较小剩余体积,也就是F[j]=F[j-V[i]]+W[i](j为剩余体积,i为物品种类),而较小剩余体积不可能利用较大剩余体积的答案,就可以从较大剩余体积向较小剩余体积递推.
- 检验一下当前思路,虽然考虑了剩余体积,但没有考虑物品重复选择的问题。如果F[较大]使用了序号为i的物品,那么F[较大]只有可能为F[较小]+W[i]。由于是从较大体积递推到较小体积,所以F[较小]=F[更小]+W[i],以此类推.
完全背包的思想:
- 完全背包问题不需要考虑单个种类物品拿的数量,因此只需要别反向递推,弄巧成拙即可。换句话说,反向递推仅是01背包的避免同种物品反复被选择的权益之计罢了.
背包DP的例题:
- P1060 开心的金明(普及-)(01背包)
- P1048 采药(普及-)(01背包)
- P1164 小A点菜 (01背包)
- P1049 装箱问题 (01背包)
- P1510 精卫填海(普及/提高-)(01背包)
- P1616 疯狂的采药(普及-)(完全背包)