动态规划之完全背包问题
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背包)
- 使用一个N*V的二维数组记录dp[i][j]
- 初始化:
dp = [[0 for _ in range(max_V+1)] for _ in range(N)]
- 边界:i=0 或j=0 时,dp[i][j] = 0
- 外层循环:i种物品,从1到N循环
- 内层循环:容量V,从1到V循环
- j < vi时,第i种物品放入容量为j的背包,因此
dp[i][j] = dp[i-1][j]
- j >= vi时,状态转移方程:
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]前更新,顺序更新即可。
步骤总结
- 使用一个长度为V的一维数组记录dp[j],初始化:dp = [0 for _ in range (V+1)]
- 边界:dp[0] = 0
- 外层循环:i种物品,从0到N-1循环
- 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])
输出结果: