Appearance
01 背包
给定
个物品,第 个物品价值为 , 体积为 。现有容积为 的背包,求将物品装入背包得到的最大价值。
- 不选第
个物品: ; - 选第
个物品: 。
问题的解是
cpp
for (int i = 1; i <= n; i ++) {
for (int v = 0; v <= m; v ++) {
f[i][v] = f[i - 1][v];
if (v >= w[i])
f[i][v] = max(f[i][v], f[i - 1][v - w[i]] + c[i]);
}
}
空间优化
实际上,状态转移方程的第一维可以去掉,即让新状态覆盖旧状态,降低空间开销。
但在程序中,直接移除第一维会导致错误。令
在内层循环中,
cpp
for (int i = 1; i <= n; i ++)
for (int v = m; v >= w[i]; v --) // 倒序枚举
f[v] = max(f[v], f[v - w[i]] + c[i]);
完全背包
基于 01 背包,每种物品可装无限次。
- 不选第
种物品: ; - 多选一个第
种物品: 。
时间复杂度:
cpp
for (int i = 1; i <= n; i ++) {
for (int v = 0; v < m; v ++) {
f[i][v] = f[i - 1][v];
if (v >= w[i])
f[i][v] = max(f[i][v], f[i][v - w[i]] + c[i]);
}
}
空间优化
参照 01 背包 - 空间优化 中的错误做法:直接移除第一维,并升序枚举
cpp
for (int i = 1; i <= n; i ++)
for (int v = w[i]; v <= m; v ++)
f[v] = max(f[v], f[v - w[i]] + c[i]);
多重背包
基于 01 背包,第
种物品可装 次。
在内层循环中,分别绑定
时间复杂度:
cpp
for (int i = 1; i <= n; i ++)
for (int v = m; v >= w[i]; v --)
for (int k = 0; k <= s[i] && v >= k * w[i]; k ++)
f[v] = max(f[v], f[v - k * w[i]] + k * c[i]);
二进制优化
- 任意正整数
可拆分为 ( 且 尽量大); - 相反,
能且仅能组合出 中的所有整数。
将
组别 | ||||
---|---|---|---|---|
总体积 | ||||
总价值 |
此
时间复杂度:
cpp
for (int i = 1; i <= n; i ++) {
int wi, ci, s;
cin >> wi >> ci >> s;
for (int k = 1; k <= s; k *= 2) {
w[++ cnt] = k * wi, c[cnt] = k * ci;
s -= k;
}
if (s)
w[++ cnt] = s * wi, c[cnt] = s * ci;
}
for (int i = 1; i <= tot; i ++)
for (int v = m; v >= w[i]; v --)
f[v] = max(f[v], f[v - w[i]] + c[i]);
分组背包
背包体积为
,有 组物品,第 组有 个,其中第 个体积为 ,价值为 。每组只能取走一个物品。求将物品装入背包得到的最大价值。
- 不选第
组: ; - 选第
组:$ f[k,v]=\max_{1\leq i\leq s_k}\ f[k-1,v-w_{ki}]+c_{ki}$。
可以省去第一维。时间复杂度:
cpp
for (int k = 1; k <= t; k ++)
for (int v = m; v >= 0; v --)
for (int i = 1; i <= s[k]; i ++)
if (v >= w[k][i])
f[v] = max(f[v],f[v - w[k][i]] + c[k][i]);
二维背包
背包体积为
,承重为 。有 个物品,第 个体积为 ,质量为 ,价值为 。求将物品装入背包得到的最大价值。
时间复杂度为
cpp
for (int i = 1; i <= n; i ++)
for (int p = V; p >= v[i]; p --)
for (int q = M; q >= m[i]; q --)
f[p][q] = max(f[p][q], f[p - v[i]][q - m[i]] + c[i]);
方案总数
基于 01 背包,求将物品放入背包的方案数。
- 不放第
个物品:有 种; - 放第
个物品:有 种。
同样可以省去第一维:
初始条件:
cpp
g[0] = 1;
for (int i = 1; i <= n; i ++)
for (int v = m; v >= w[i]; v --)
g[v] += g[v - w[i]];
最优方案总数
基于 01 背包,求最优方案数。
已知 01 背包 的状态转移方程:
- 若
,不放物品 最优: ; - 若
,放物品 最优: ; - 若相等,则两种方案都最优:
。
cpp
for (int i = 1; i <= n; i ++) {
for (int v = 0; v <= m; v ++) {
f[i][v] = f[i - 1][v];
if (v >= w[i])
f[i][v] = max(f[i][v], f[i - 1][v - w[i]] + c[i]);
if (f[i][v] == f[i - 1][v])
g[i][v] += g[i - 1][v];
if (f[i][v] == f[i - 1][v - w[i]] + c[i])
g[i][v] += g[v - w[i]];
}
}
省去第一维:
cpp
for (int i = 1; i <= n; i ++) {
for (int v = m; v >= w[i]; v --) {
int maxn = max(f[v], f[v - w[i]] + c[i]);
int maxg = 0; // 最优方案数
if (maxn == f[v])
maxg += g[v];
if (maxn == f[v - w[i]] + c[i])
maxg += g[v - w[i]];
f[v] = maxn, g[v] = maxg;
}
}