问题的提出:有N种物品,一个容量为v的背包,每种物品可以无限的加入背包,第i种物品的价值为v[i]花费为w[i],求将那些装入背包能让价值最大且不超过背包的容量?
思路:该题类似于01背包,唯一的不同就是所有的物品可以无限取,这样从物品的角度考虑就会有多中方法,不像01中的取或不取;当然01背包作为最基本的背包问题,所以我们也可以将完全背包转化为01背包来做,按照01背包思路,我们将f[i][ v]作为选了i种物品放入背包为v的背包中最大价值,所以我们得到了这样的方程:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
;
优化方法:
如果有两件物品 i, j满足c[i]<=c[j]且w[i]>=w[j],我们可以将物品j丢弃不考虑,另外的就是去掉大于v的物品,然后就是将一件花费高的物品转化为多件花费低的物品来比较两种方法的价值高低,取价值高的那种;
转换:
上面提到01背包式最简单的背包,按道理讲应该是背包都可以转化为01背包,其实这个想法是对的,只是有些问题转化为01背包不好解罢了!但这个完全背包转化为01背包来解会优化很多;还是01背包的想法,如果我能将2维数组转化为1维数组,那样不就优化了很多所以我们得到了下面的代码:
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-cost]+weight}
是不是觉得这个代码可眼熟,是的这个和01背包的一维数组优化代码很相似,唯一不同的是v是从0到V;为什么会有这儿顺序?我们先从01背包说起,因为01背包要求每种物品只可以选一次,所以v从V到0,这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。
值得一提的是,上面的伪代码中两层for循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。
总结
完全背包问题也是一个相当基础的背包问题,它有两个状态转移方程,分别在“基本思路”以及“O(VN)的算法“的小节中给出。希望你能够对这两个状态转移方程都仔细地体会,不仅记住,也要弄明白它们是怎么得出来的,最好能够自己想一种得到这些方程的方法。事实上,对每一道动态规划题目都思考其方程的意义以及如何得来,是加深对动态规划的理解、提高动态规划功力的好方法。