背包问题九讲,从基础到进阶,彻底掌握动态规划
在算法学习的道路上,背包问题是一类非常经典的动态规划问题,它不仅在编程竞赛中频繁出现,也在实际应用中有着广泛的应用场景,本文将带你从基础到进阶,全面了解背包问题的九种常见变体及其解法,帮助你在动态规划领域更上一层楼。
0/1 背包问题
问题描述:
给定 \( n \) 个物品,每个物品有一个重量 \( w_i \) 和一个价值 \( v_i \),以及一个容量为 \( W \) 的背包,每个物品只能选择放入或不放入背包中,目标是使得背包中物品的总价值最大。
解法:
使用二维数组dp[i][j] 表示前 \( i \) 个物品在容量为 \( j \) 时的最大价值,状态转移方程为:
\[ dp[i][j] = \max(dp[i-1][j], dp[i-1][j-w_i] + v_i) \]
为了优化空间复杂度,可以使用一维数组dp[j] 进行滚动更新,注意内层循环需要逆序遍历:
def knapsack_01(weights, values, W):
n = len(weights)
dp = [0] * (W + 1)
for i in range(n):
for j in range(W, weights[i] - 1, -1):
dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
return dp[W]完全背包问题
问题描述:
与 0/1 背包类似,但每个物品可以无限次选择放入背包中。
解法:
同样使用一维数组dp[j],但内层循环需要正序遍历:
def knapsack_complete(weights, values, W):
n = len(weights)
dp = [0] * (W + 1)
for i in range(n):
for j in range(weights[i], W + 1):
dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
return dp[W]多重背包问题
问题描述:

每个物品有固定的数量限制,即每个物品可以放入背包中的次数有限。
解法:
可以将每个物品拆分成多个 0/1 背包问题来解决,将第 \( i \) 个物品拆分成若干个重量和价值分别为 \( 2^k \times w_i \) 和 \( 2^k \times v_i \) 的物品,\( k \) 满足 \( 2^k \leq c_i \)。
def knapsack_multiple(weights, values, counts, W):
n = len(weights)
new_weights = []
new_values = []
for i in range(n):
k = 1
while k <= counts[i]:
new_weights.append(k * weights[i])
new_values.append(k * values[i])
counts[i] -= k
k *= 2
if counts[i] > 0:
new_weights.append(counts[i] * weights[i])
new_values.append(counts[i] * values[i])
return knapsack_01(new_weights, new_values, W)混合背包问题
问题描述:
同时包含 0/1 背包、完全背包和多重背包的混合问题。
解法:
分别处理每种类型的物品,使用相应的解法。
def knapsack_mixed(items, W):
n = len(items)
dp = [0] * (W + 1)
for item in items:
if item['type'] == '01':
for j in range(W, item['weight'] - 1, -1):
dp[j] = max(dp[j], dp[j - item['weight']] + item['value'])
elif item['type'] == 'complete':
for j in range(item['weight'], W + 1):
dp[j] = max(dp[j], dp[j - item['weight']] + item['value'])
elif item['type'] == 'multiple':
k = 1
while k <= item['count']:
for j in range(W, k * item['weight'] - 1, -1):
dp[j] = max(dp[j], dp[j - k * item['weight']] + k * item['value'])
item['count'] -= k
k *= 2
if item['count'] > 0:
for j in range(W, item['count'] * item['weight'] - 1, -1):
dp[j] = max(dp[j], dp[j - item['count'] * item['weight']] + item['count'] * item['value'])
return dp[W]二维费用背包问题
问题描述:
每个物品有两个费用 \( w_i \) 和 \( v_i \),背包也有两个容量 \( W \) 和 \( V \),目标是在不超过两个容量的情况下最大化物品的总价值。

解法:
使用三维数组dp[i][j][k] 表示前 \( i \) 个物品在容量分别为 \( j \) 和 \( k \) 时的最大价值,状态转移方程为:
\[ dp[i][j][k] = \max(dp[i-1][j][k], dp[i-1][j-w_i][k-v_i] + value_i) \]
def knapsack_2d(weights, values, costs, W, V):
n = len(weights)
dp = [[[0] * (V + 1) for _ in range(W + 1)] for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(W + 1):
for k in range(V + 1):
dp[i][j][k] = dp[i-1][j][k]
if j >= weights[i-1] and k >= costs[i-1]:
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-weights[i-1]][k-costs[i-1]] + values[i-1])
return dp[n][W][V]分组背包问题
问题描述:
每个物品属于一个组,每组中最多选择一个物品放入背包。
解法:
使用二维数组dp[i][j] 表示前 \( i \) 组物品在容量为 \( j \) 时的最大价值,状态转移方程为:
\[ dp[i][j] = \max(dp[i-1][j], dp[i-1][j-w_{i,k}] + v_{i,k}) \]
def knapsack_group(groups, W):
n = len(groups)
dp = [0] * (W + 1)
for group in groups:
for j in range(W, -1, -1):
for item in group:
if j >= item['weight']:
dp[j] = max(dp[j], dp[j - item['weight']] + item['value'])
return dp[W]有依赖的背包问题
问题描述:

每个物品有一个依赖关系,只有当其依赖的物品被放入背包后,该物品才能被放入背包。
解法:
使用树形结构表示依赖关系,先处理没有依赖的物品,再逐层处理依赖的物品。
from collections import defaultdict
def knapsack_dependent(items, dependencies, W):
graph = defaultdict(list)
indegree = {item: 0 for item in items}
for u, v in dependencies:
graph[u].append(v)
indegree[v] += 1
queue = [item for item in items if indegree[item] == 0]
dp = [0] * (W + 1)
while queue:
next_queue = []
for item in queue:
weight, value = items[item]['weight'], items[item]['value']
for j in range(W, weight - 1, -1):
dp[j] = max(dp[j], dp[j - weight] + value)
for neighbor in graph[item]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
next_queue.append(neighbor)
queue = next_queue
return dp[W]树形背包问题
问题描述:
物品之间有树形依赖关系,每个节点可以包含多个子节点,每个子节点的物品可以选择是否放入背包。
解法:
使用深度优先搜索(DFS)遍历树,计算每个节点的最优解。
def dfs(node, parent, weights, values, children, W, memo):
if (node, W) in memo:
return memo[(node, W)]
dp = [0] * (W + 1)
相关文章
