动态规划之完全背包问题

本文理论内容部分参考自崔添翼的背包九讲系列acwing

01背包问题可参考上一篇动态规划之01背包问题

一、完全背包问题

有N种物品和一个容量为V的背包,第i种物品的体积为\(v_i\),价值为\(w_i\),每种物品有无限个(相当于可重复使用)。求解将哪些物品放入背包可以达到总价值最大。

二、基本思路

这个问题非常类似01背包问题,特点是:每种物品有无数件,可以选择0件、1件、2件...直到\(V/v_i\)
子问题定义状态:即\(F[i,j]\)表示前i件物品放入容量为j的背包,可以获得的最大价值。

状态转移方程

\[F[i,j] = max\{F[i-1,j-kv_i] + kw_i|0≤kv_i≤j\} \]

这跟01背包问题一样有O(VN) 个状态需要求解,求解状态F[i,j]的时间是\(O(j/v_i)\),总的复杂度可以认为是\(O(NV\sum \frac V{v_i}))\),需要改进。

三、闫氏dp分析法

根据以上状态转移方程:

\(F[i,j] = max\{F[i-1,j], F[i-1,j-v_i] + w_i, F[i-1,j-2v_i] + 2w_i, ...\}\)

\(F[i,j-v_i] = max\{F[i-1,j-v_i], F[i-1,j-2v_i] + w_i, F[i-1,j-3v_i] + 2w_i, ...\}\)

可以得到优化后的状态转移方程:

\[F[i,j] = max\{F[i-1,j], F[i, j-v_i] + w_i\} \]

对比01背包问题,只有表达式中的i与i-1有差异:

\[F[i,j] = max\{F[i-1,j], F[i-1,j-v_i] + w_i\} \]

步骤总结(类似01背包)

  1. 使用一个N*V的二维数组记录dp[i][j]
  2. 初始化:dp = [[0 for _ in range(max_V+1)] for _ in range(N)]
  3. 边界:i=0 或j=0 时,dp[i][j] = 0
  4. 外层循环:i种物品,从1到N循环
  5. 内层循环:容量V,从1到V循环
  6. j < vi时,第i种物品放入容量为j的背包,因此dp[i][j] = dp[i-1][j]
  7. j >= vi时,状态转移方程:
  8. dp[i][j] = max(dp[i-1][j], dp[i][j-vi] + wi)

python版代码

# 4种物品属性
v = [0, 1, 2, 3, 4]
w = [0, 2, 4, 4, 5]

N = len(v)
max_V = 5
dp = [[0 for _ in range(max_V+1)] for _ in range(N)]
for i in range(1, N):
    for j in range(1, max_V+1):
        if j < v[i]:
            dp[i][j] = dp[i-1][j]
        else:
            dp[i][j] = max(dp[i-1][j], dp[i][j-v[i]] + w[i])

print(dp[N-1][max_V])

输出结果:

四、优化空间复杂度

一维数组的更新方式:
\(j >= v_i\)时,\(dp[j] = max(dp[j], dp[j-v[i]] + w[i])\)
由于v[i]是正数,dp[i][j-v[i]]在dp[i][j]前更新,顺序更新即可。

步骤总结

  1. 使用一个长度为V的一维数组记录dp[j],初始化:dp = [0 for _ in range (V+1)]
  2. 边界:dp[0] = 0
  3. 外层循环:i种物品,从0到N-1循环
  4. j >= vi时,状态转移方程:dp[j] = max(dp[j], dp[j-v[i]] + w[i])

python版代码

# 物品属性
v = [1, 2, 3, 4]
w = [2, 4, 4, 5]

N = len(v)
V = 5

dp = [0 for _ in range(V + 1)]
for i in range(N):
    for j in range(v[i], V + 1):
        dp[j] = max(dp[j], dp[j - v[i]] + w[i])

print(dp[V])

输出结果:

posted @ 2021-06-11 23:34  ikventure  阅读(95)  评论(0编辑  收藏  举报