跳到主要内容

BISHI136. 【模板】01背包

题目描述​

你有一个背包,最大容量为 V。现有 n 件物品,第 i 件物品的体积为 w[i]、 价值为 v[i]。研究人员提出以下两种装填方案:

  1. 不要求装满背包,求能获得的最大总价值;
  2. 要求最终恰好装满背包,求能获得的最大总价值。若不存在恰好装满的 装法,则答案记为 0。

输入格式​

第一行输入两个整数 n 和 V,分别表示物品数量与背包容量。 此后 n 行,第 i 行输入两个整数 w[i] 和 v[i],分别表示第 i 件物品的体积与价值。

输出格式​

输出两行:第一行输出方案 1 的答案;第二行输出方案 2 的答案(若无解输出 0)。

示例​

示例 1​

输入:

3 5
2 10
4 5
1 4

输出:

14
9

说明: 选择第 1、3 件物品可得最大价值 10 + 4 = 14(未装满); 选择第 2、3 件物品时体积 4 + 1 = 5 恰好装满,价值 5 + 4 = 9。

示例 2​

输入:

3 8
12 6
11 8
6 8

输出:

8
0

说明: 装第三件物品时总价值最大(8)但不满;恰好装满背包无解,输出 0。


解题思路​

第一步:理解问题本质​

01 背包:每件物品只有「选」或「不选」两种状态,选了的物品体积之和 不能超过容量 V,求价值之和的最大值。两问的唯一区别是方案 2 要求 体积之和恰好等于 V。

第二步:二维 DP​

设 dp[i][j] 表示「前 i 件物品、容量 j 下能获得的最大价值」:

dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])

答案是 dp[n][V]。正确但空间 O(n·V)。

第三步:滚动数组压成一维(最优)​

观察转移只用到「上一行」的数据,可以把第一维去掉。 但此时内层容量必须从大到小遍历:

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

倒序保证 dp[j-w] 用的是「还没放第 i 件物品」时的旧值; 如果正序遍历,dp[j-w] 可能已经被本轮更新过,就会把同一件物品 用多次,变成完全背包。

第四步:两问的差异只在初值​

  • 不要求装满:dp 全部初始化为 0。「容量 j 装不满」也是合法状态,价值为 0。
  • 恰好装满:只有 dp[0] = 0,其余初始化为 -∞(表示不可达)。 这样只有能一步步从 0 转移过来的容量才有意义;最终 dp[V] 仍为 -∞ 就说明装不满,按题意输出 0。

完整代码实现​

算法可视化 · 逐帧回放这道题的代码67 帧 · 录制输入
import sys

NEG = float('-inf')


def solve() -> None:
"""01 背包:分别求「不必装满」与「恰好装满」的最大价值"""
data = sys.stdin.read().split()
if not data:
return
idx = 0
n = int(data[idx]); V = int(data[idx + 1]); idx += 2
items = []
for _ in range(n):
w = int(data[idx]); v = int(data[idx + 1]); idx += 2
items.append((w, v))

# 方案一:不要求装满
dp1 = [0] * (V + 1)
# 方案二:恰好装满(-inf 表示该容量尚不可达)
dp2 = [NEG] * (V + 1)
dp2[0] = 0

for w, v in items:
for j in range(V, w - 1, -1): # 倒序:保证每件物品只用一次
dp1[j] = max(dp1[j], dp1[j - w] + v)
if dp2[j - w] != NEG:
dp2[j] = max(dp2[j], dp2[j - w] + v)

ans1 = dp1[V]
ans2 = dp2[V] if dp2[V] != NEG else 0
sys.stdout.write(f"{ans1}\n{ans2}\n")


if __name__ == "__main__":
solve()

示例推演​

以示例 1(容量 5,物品 (2,10)、(4,5)、(1,4))为例,方案二 dp2 随物品更新:

物品dp2[0]dp2[1]…dp2[4]dp2[5]说明
初始0-∞…-∞-∞只有容量 0 可达
(2,10)0-∞…-∞-∞体积 2 → 10
(4,5)0-∞…5-∞体积 4 → 5
(1,4)04…994+1 → 9,恰好装满 5

最终 dp2[5] = 9。方案一 dp1[5] = 14(选 1、3 号)。


复杂度分析​

解法时间复杂度空间复杂度说明
二维 DPO(n·V)O(n·V)直观
一维滚动O(n·V)O(V)推荐,本题要求

易错点总结​

1. 内层容量写成正序​

正序会让同一件物品被反复使用,答案偏大 —— 这是 01 背包最经典的错误。 务必 for j in range(V, w-1, -1)。

2. 恰好装满的初值​

方案二必须用 -∞ 初始化,否则「凑不出恰好 V」会被当成有效状态而误判。

3. 两问共用一个 dp 数组​

两问初值不同,不能共用;分别开 dp1、dp2 才清晰。


扩展思考​

  • 完全背包(每件物品可取无限次)对应内层正序遍历。
  • 分组背包、多重背包都能在一维写法基础上加一层循环变形。
  • 若还要输出具体选了哪些物品,需要额外记录决策路径。

相关题目​

自测​

加载评论中...