《DP学习系列》从零开始学习动态规划,多重背包(三)

1. 题目

有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci,价值是Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。

2. 转化为完全背包

因为对于第i种物品有Mi+1种策略:取0件,取1件……取Mi件。令F[i;v]表示前i种物品恰放入一个容量为v的背包的最大价值,则有状态转移方程:

F [ i v ] = m a x { F [ i 1 ; v k C i ] + k W i | 0 k M i }

  和上一节的伪代码差不多一样(背包九讲未给出):

for n=1 to N
   for v=0 to V
      for  k=0 to Mi
           F[i,v]=F[i-1,v-kCi]+kWi
           if F[i,v] > last F[i,v]
               update F[i,v]

  二进制优化以后,与原版背包九讲稍有区别:

for n=1 to N
   for v=0 to V
      for  k=0 to logMi
           F[i,v]=F[i-1,v-2^kCi]+2^kWi
           if F[i,v] > last F[i,v]
               update F[i,v]

  经过优化后的时间复杂度为 O ( V l o g M i ) ,该界比 O ( N V m a x { l o g M i } ) 更加准确,可以思考为什么,比较简单不赘述。

3. 变种问题

有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci,求解将哪些物品装入背包可使这些物品的耗费的空间恰好等于背包容量。

解题思路

它的基本思想是这样的:设 F [ i , j ] 表示“用了前i种物品填满容量为 j 的背包后,最多还剩下几个第i种物品可用”,如果 F [ i , j ] = 1 则说明这种状态不可行,若可行应满足 0 F [ i , j ] M i

  在这个变种问题,应该要多思考下,背包九讲在这里直接给了伪代码,没有解释太多。按照之前的步骤,首先针对这个问题找到最优子结构: F [ i , j ] 表示“用了前i种物品填满容量为 j 的背包后,最多还剩下几个第i种物品可用。”
  然后思考该最优子结构的状态转移方程,(这里如果自己分析出来,才代表真正理解了作者的用意):

  • F [ i , j ] F [ i 1 , j ] 虽然不存在直接关系,但存在以下关系:

    F [ i , j ] = { M i ,     i f   F [ i 1 , j ] 0 1 ,     i f   F [ i 1 , j ] < 0

  • F [ i , j ] F [ i , j k C i ] 存在以下关系:

    F [ i , j ] = F [ i , j k C i ] k , 0 < k < M i


  所以,一个完整的状态转移应该分两步,首先由 F [ i 1 , j ] 转移到 F [ i , j ] = M i 1 ,然后再由 F [ i , j ] = M 转移到 F [ i , j + k c i ] k ,后一步最关键,前一步算是后一步的铺垫。现在再看背包九讲给出的伪代码就很好理解了:

F[0;1....V ] −1
F[0;0]        0

for i=1 to N
    for j=0 to V
        if F[i − 1][j] ≥ 0
            F[i][j] = Mi
        else
            F[i][j] = −1
        for j=0 to V − Ci//j+Ci<V 得到j<V-Ci
            if F[i][j] > 0
                F[i][j + Ci]=max{F[i][j + Ci], F[i][j] − 1}

4. 总结

  将变种问题的状态转移方程仔细与前面两篇记录的方程对比,不难发现转移方程必须更新 F [ i , 0 v ] 的所有状态,那么由我们初学者感慨下:DP是用空间换时间的算法,定义状态其实就是找到子结构,该子结构可以推导出其他子结构。最后问题的答案可以通过遍历这些子结构得到。
  以前一直很纠结DP,因为听起来很高大上。其实说白了,关键就是定义子结构和找到每个子结构之间的关系。

猜你喜欢

转载自blog.csdn.net/LoveStackover/article/details/80581907