前言
如果您也是参考笔者的记录学习,那么推荐您至少先看一遍原版背包九讲。本系列只是初学者对若干问题的补充和思考。
1. 题目
有N种物品和一个容量为V的背包,每种物品都有无限件可用。放入第i种物品的费用是 ,价值是 。求解:将哪些物品装入背包,可使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。
2. 基本思路
如果仍然按照解01背包时的思路,令 表示前i种物品恰放入一个容量为 的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
原文没有写出来基本思路的伪代码,笔者来写下(写出这段伪代码,才代表真正理解上面的转移方程,才可能解决一些变种问题):
for n=1 to N
for v=0 to V
for k=0 to V/Ci
F[i,v]=F[i-1,v-kCi]+kWi
if F[i,v] > last F[i,v]
update F[i,v]
3. 优化基本思路
3.1 输入优化
我们知道如果一个物件 最大, 最小,那么可以直接得出答案: 个该物件就是最佳答案,但是往往不可能做到这步。所以崔作者给出的优化是一种局部优化,具体为:
首先将费用大于V 的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的是哪个,可以 地完成这个优化。
任意的稳定排序算法都是可以的(理解到这一步,才代表真正理解输入优化的本质),崔作者推荐计数排序,其时间复杂度最优。伪代码如下:
counter_sort(N,W)//按照Wi计数排序一次,
counter_sort(N,C)//然后再按照Ci排序一次,此时Vi相同的元素保持上一次计数排序的偏序,我们只要取Vi相同元素中最后一个值就可以。
for j=0,i=0 to N //遍历结果数组
while(output[i] == output[i+1]) i++;
newinput[j++]=output[i];
3.2 时间复杂度优化
二进制优化:因为,不管最优策略选几件第i种物品,其件数写成二进制后,总可以表示成若干个 件物品的和。这样一来就把每种物品拆成 件物品,是一个很大的改进。
这里不好理解,笔者一开始大方向对了,理解该优化的本质,对于理解DP有很大帮助。还是观察状态转移方程:
,注意
是最初定义的最优子问题,那么它具有一定的性质:其代表当前i,v的下的最佳解。而最优子问题逐步递推出最终问题的答案。
这是笔者的理解,将来再看《算法导论》时,会更加准确的描述这种性质。回到这个问题中,
,因为
,那么我们得到最终的K时,由所有的
子问题经过选择得到(该
项系数可能为1,也可能为0)。所以状态转换公式可以为
,根据最优子问题(结构)的特点,我们可以说最优解的K能取到
的任何值(不代表K需要遍历整个区间,K代表该区间的最优解的值)。可以通过举列子说明,比如K=5,那么遍历2^k时,2^0取,2^1不取,2^2取。优化之后的伪代码如下:
for n=1 to N
for v=0 to V
for k=0 to log(V/Ci)
F[i,v]=F[i-1,v-2^kCi]+2^kWi
if F[i,v] > last F[i,v]
update F[i,v]
4. 的算法
F[0...V] 初始化为 0
for i=1 to N
for v=Ci to V //增序
F [v] = max{F[v], F [v − Ci] + Wi}
上一篇01背包问题稍微改变。至于该方式的理解,现在比较多的是后验逻辑,也就是根据结果理解过程。假设该结论是对的,那么推出必要条件:
首先想想为什么01背包中要按照v递减的次序来循环。让v递减是为了保证第i次循环中的状态 是由状态 递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果 。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果 ,所以就可以并且必须采用v递增的顺序循环。这就是这个简单的程序为何成立的道理。
如果是第一个推出来该结论的人,可能需要严格的逻辑证明,但是这里可以用必要条件来理解,但是不够充分。目前很多理解都是当结论记住,按照学习梯度,现在暂时还是不要深究。
5. 总结
完全背包问题至少应该理解基础方法和优化策略,至于一维状态转化方程,可以当模板记住。因为这个问题如果要用充分条件推导,恐怕难度已经不是初学DP可以解决的了。