《DP学习系列》从零开始学习动态规划,完全背包(二)

前言

如果您也是参考笔者的记录学习,那么推荐您至少先看一遍原版背包九讲。本系列只是初学者对若干问题的补充和思考。

1. 题目

有N种物品和一个容量为V的背包,每种物品都有无限件可用。放入第i种物品的费用是 C i C_{i} Ci,价值是 W i W_{i} Wi。求解:将哪些物品装入背包,可使这些物品的耗费的费用总和不超过背包容量,且价值总和最大。

2. 基本思路

如果仍然按照解01背包时的思路,令 F [ i , v ] F[i,v] F[i,v]表示前i种物品恰放入一个容量为 v v v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
F [ i , v ] = m a x { F [ i − 1 , v − k C i ] + k W i } , 0 ≤ k C i ≤ v F[i, v] = max\{F [i − 1, v − kC_{i}] + kW_{i} \} , 0 ≤ kCi ≤ v F[i,v]=max{F[i1,vkCi]+kWi},0kCiv

原文没有写出来基本思路的伪代码,笔者来写下(写出这段伪代码,才代表真正理解上面的转移方程,才可能解决一些变种问题):

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 输入优化

我们知道如果一个物件 W i W_{i} Wi最大, C i C_{i} Ci最小,那么可以直接得出答案: V / C i V/Ci V/Ci个该物件就是最佳答案,但是往往不可能做到这步。所以崔作者给出的优化是一种局部优化,具体为:

首先将费用大于V 的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的是哪个,可以 O ( V + N ) O(V + N) O(V+N)地完成这个优化。

任意的稳定排序算法都是可以的(理解到这一步,才代表真正理解输入优化的本质),崔作者推荐计数排序,其时间复杂度最优。伪代码如下:

  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种物品,其件数写成二进制后,总可以表示成若干个 2 k 2^k 2k件物品的和。这样一来就把每种物品拆成 O ( l o g V / C i ) O(log V /Ci) O(logV/Ci)件物品,是一个很大的改进。

这里不好理解,笔者一开始大方向对了,**理解该优化的本质,对于理解DP有很大帮助。**还是观察状态转移方程: F [ i , v ] = F [ i − 1 , v − k C i ] + k W i F[i,v]=F[i-1,v-kC_{i}]+kW_{i} F[i,v]=F[i1,vkCi]+kWi注意 F [ i , v ] F[i,v] F[i,v]是最初定义的最优子问题,那么它具有一定的性质:其代表当前i,v的下的最佳解。而最优子问题逐步递推出最终问题的答案。
  这是笔者的理解,将来再看《算法导论》时,会更加准确的描述这种性质。回到这个问题中, F [ i , v ] = F [ i − 1 , v − K C i ] + K W i F[i,v]=F[i-1,v-KC_{i}]+KW_{i} F[i,v]=F[i1,vKCi]+KWi,因为 K = a 2 k + b 2 k − 1 + . . . m 2 0 K=a2^k+b2^{k-1}+...m2^0 K=a2k+b2k1+...m20,那么我们得到最终的K时,由所有的 2 k 2^k 2k子问题经过选择得到(该 2 k 2^k 2k项系数可能为1,也可能为0)。所以状态转换公式可以为 F [ i , v ] = F [ i − 1 , v − 2 k C i ] + 2 k W i F[i,v]=F[i-1,v-2^kC_{i}]+2^kW_{i} F[i,v]=F[i1,v2kCi]+2kWi,根据最优子问题(结构)的特点,我们可以说最优解的K能取到 0 − V / C i 0-V/Ci 0V/Ci的任何值(不代表K需要遍历整个区间,K代表该区间的最优解的值)。可以通过举列子说明,比如K=5,那么遍历2k时,20取,21不取,22取。优化之后的伪代码如下:

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. O ( V N ) O(VN) O(VN)的算法

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次循环中的状态 F [ i , v ] F[i, v] F[i,v]是由状态 F [ i − 1 , v − C i ] F[i − 1,v − Ci] F[i1,vCi]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果 F [ i − 1 , v − C i ] F [i − 1, v − Ci] F[i1,vCi]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果 F [ i , v − C i ] F[i,v − Ci] F[i,vCi],所以就可以并且必须采用v递增的顺序循环。这就是这个简单的程序为何成立的道理。

如果是第一个推出来该结论的人,可能需要严格的逻辑证明,但是这里可以用必要条件来理解,但是不够充分。目前很多理解都是当结论记住,按照学习梯度,现在暂时还是不要深究。

5. 总结

完全背包问题至少应该理解基础方法和优化策略,至于一维状态转化方程,可以当模板记住。因为这个问题如果要用充分条件推导,恐怕难度已经不是初学DP可以解决的了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值