01背包or完全背包
01背包:每个物品只能选1次或不选。
\(n\):物品的总个数。
\(W\):背包的最大总容量
\(w[i]\):第 i 件物品的重量/体积
\(v[i]\):第 i 件物品的价值 (value)。
\(dp[j]\):当容量限制为 j 时,能获得的最大价值。
当我们计算 \(dp[j]\) 时,我们希望 \(dp[j - w]\) 是还没有放入第 i 个物品时的状态。
// 01 背包
for (int i = 1; i <= n; i++) { // 1. 枚举物品
for (int j = W; j >= w[i]; j--) { // 2. 枚举容量 (必须倒序!)
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
完全背包:每个物品可以选无数次。
\(n\):物品的总个数。
\(W\):背包的最大总容量
\(w[i]\):第 i 件物品的重量/体积
\(v[i]\):第 i 件物品的价值 (value)。
\(dp[j]\):当容量限制为 j 时,能获得的最大价值。
当我们计算 \(dp[j]\) 时,我们希望 \(dp[j - w]\) 是可能已经放入了第 i 个物品的状态.
// 完全背包核心代码
for (int i = 1; i <= n; i++) { // 1. 枚举物品
for (int j = w[i]; j <= W; j++) { // 2. 枚举容量 (必须正序!)
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
if 正序:
可以无数量限制的拿取物品
if 倒序:
只能计算一次当前数据
综上
01背包倒序保护现场,完全背包正序继续叠加
多重背包
你有一个容量为 \(W\) 的背包。
有 \(N\) 种物品,第 \(i\) 种物品:
重量:\(w_i\)
价值:\(v_i\)
数量限制:\(s_i\) 个
法1:暴力拆分or循环
把第 \(i\) 种物品的 \(s_i\) 个,看成是 \(s_i\) 个"长得一模一样"的独立物品。
比如:物品A有 1000 个。我就把它拆成 A1, A2, ..., A1000。
然后对这所有拆出来的新物品跑 0/1 背包。
数过大容易TLE
二进制拆分法 (Binary Splitting)
用二进制进行优化
核心思想:
任何一个整数,都可以用一组 \(2\) 的幂次 (1, 2, 4, 8...) 的和来表示。
二进制拆分保证了数学上的完备性:这 4 个数字 (1, 2, 4, 6) 的加减组合,绝对能覆盖 0 到 13 之间的任何整数。这一点是数学保证的。
把物品拆分为二进制表示后,对它们进行01背包
即每个组只能选一次,最后你背的价值一定是你选择这个个数中价值最大的(因为有max帮你做选择)
状态转移方程依旧与01背包相同。
#include<bits/stdc++.h>
using namespace std;
// 稍微开大一点,防止越界
int dp[40005];
int n, W;
// 用数组存拆分后的物品,比vector稍微快一点点(此时区别不大)
int cnt = 0; // 记录新物品总数
int nw[100005], nv[100005]; // 拆分后的新重量、新价值
int main() {
// 优化输入输出效率
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> W;
for(int i = 1; i <= n; i++) {
int v, w, m;
cin >> v >> w >> m; // 注意题目输入顺序可能是 价值、重量、数量
// --- 二进制拆分核心 ---
int k = 1;
while(m >= k) {
cnt++;
nw[cnt] = k * w;
nv[cnt] = k * v;
m -= k;
k *= 2;
}
if(m > 0) {
cnt++;
nw[cnt] = m * w;
nv[cnt] = m * v;
}
// ---------------------
}
// --- 0/1 背包核心 ---
// 遍历所有拆出来的新物品
for(int i = 1; i <= cnt; i++) {
// 倒序枚举容量
for(int j = W; j >= nw[i]; j--) {
dp[j] = max(dp[j], dp[j - nw[i]] + nv[i]);
}
}
cout << dp[W] << endl;
return 0;
}
分组背包
\(k\) 组物品,每组里至多选 1 个 。
\(n\):物品的总个数。
\(W\):背包的最大总容量
\(w[i]\):第 i 件物品的重量/体积
\(v[i]\):第 i 件物品的价值 (value)。
\(dp[j]\):当容量限制为 j 时,能获得的最大价值。
\(group[k]\):第\(K\)组物品。
个人理解就是把每一组看成一个物品,在01背包的内层循环中,加一个遍历每个组一整组的循环。
世界就是一个巨大的01背包(bushi)
就像打游戏通关一样,第 1 组决策完,状态固定了,再进去第 2 组。所以也是倒序循环,防止组内互斥物品叠加。
// 分组背包核心代码
for (int k = 1; k <= groups; k++) { // 1. 枚举组 (Group)
for (int j = W; j >= 0; j--) { // 2. 枚举容量 (必须倒序!)
for (int i : group[k]) { // 3. 枚举组内的每一个物品
if (j >= w[i]) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
}
}