【入门级-算法-9、动态规划:简单背包类型动态规划】

一、先理解问题:什么是背包问题?

想象一个场景:

你去露营,带了一个背包,背包最多能装 5公斤 的东西。

现在有 3 件物品:

帐篷:3公斤,价值 4 元(假设是租的价格)

睡袋:2公斤,价值 3 元

食物:4公斤,价值 5 元

问题:你该怎么选,让总价值最大,但不超过 5 公斤?

这就是01背包问题(0和1表示:要么拿,要么不拿,不能拿一半)。

最笨的方法(暴力枚举):把所有组合都试一遍。

选法 总重量 总价值 可行?

什么都不拿 0 0 ✓

只拿帐篷 3 4 ✓

只拿睡袋 2 3 ✓

只拿食物 4 5 ✓

帐篷+睡袋 5 7 ✓

帐篷+食物 7 9 ✗(超重)

睡袋+食物 6 8 ✗(超重)

全拿 9 12 ✗(超重)

最好的选法:帐篷 + 睡袋 = 总价值 7

如果物品很多(比如 100 件),2¹⁰⁰ 种组合,电脑也算不出来。

所以需要动态规划来聪明地计算。

1、01背包:每个物品只能选一次

这是最基础的背包类型,其他背包都可以看作它的变体。

场景:有N个物品,每个物品只有一个,选或不选。背包容量为M,每个物品有体积vi和价值wi,求最大价值。

核心思想:用dpj表示容量为j的背包能装的最大价值。考虑第i个物品时,只有两种选择:

不选:dpj保持不变(继承前i-1个物品的结果)

选:dpj = dpj - v\[i] + wi(腾出空间装它)

状态转移方程:dpj = max(dpj, dpj - v\[i] + wi)

关键细节------为什么容量要逆序遍历?

因为每个物品只能取一次,逆序保证dpj - v\[i]还是"前i-1个物品"的状态,而不是已经拿过第i个物品的状态。如果正序,可能会出现一个物品被重复使用多次的情况。

// 01背包核心代码

for (int i = 1; i <= N; i++) {

for (int j = M; j >= vi; j--) { // 逆序!

dpj = max(dpj, dpj - v\[i] + wi);

}

}

  1. 完全背包:每个物品无限供应
    场景:每个物品有无限个,可以随便取。
    核心变化:既然物品可以无限取,dpj - v\[i]应该允许"已经拿过第i个物品",所以容量正序遍历。

// 完全背包核心代码

for (int i = 1; i <= N; i++) {

for (int j = vi; j <= M; j++) { // 正序!

dpj = max(dpj, dpj - v\[i] + wi);

}

}

直观理解:01背包倒序是"怕自己拿自己",完全背包正序是"允许反复拿自己"。