文章目录
1. 0-1背包
N件物品,容量为V的背包,每件物品有各自价值且只能被选择一次,求有限背包容量下装入物品总价值最大。
1.1 二维版本
1.1.1 思路
N: 物品数量
V: 背包容积
vi: 第i件物品的体积
wi:第i件物品的价值
i: 第i件物品
j : 背包容量
- 状态f[i][j]定义:前i个物品中,背包容量j下的最大价值。注意:j表示体积。 初始状态f[0][0] = 0
- 当前状态依赖于之前状态,N件物品N次决策,每次对当前物品进行决策,f[i][j]由之前状态而来。
- 当前背包容量不够(j < v[i]),也就是当前物品选不了。前i个物品的最值为前i-1个的最值。f[i][j] = f[i - 1][j]
- 当前背包容量够,存在两种状态:选:f[i][j] = f[i - 1][j - v[i]] + w[i] 不选:f[i][j] = f[i - 1][j]
1.1.2 代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N][N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; i ++) { //遍历背包数
for (int j = 1; j <= m; j ++) { //从1开始遍历体积
if (j < v[i]) { // 当前背包容量不够
f[i][j] = f[i - 1][j];
} else { // 取选与不选两者中的最大值
f[i][j] = max(f[i - 1][j - v[i]] + w[i], f[i - 1][j]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
1.2 一维版本
1.2.1 思路
二维版本f[i][j]可以求得任意合法的i和j的最优解。只关注最终状态f[n][m],观察二维公式f[i][j] = f[i - 1][jxxxx],s所以可以采用一维数组。
- 初始状态定义改变:N件物品,背包容量j下的最优解。
- f[j] = max(f[j], f[i - 1][j - v[i]] + w[i]) 从小到大更新时,如果还是正序,则有f[较小体积]更新到f[较大体积],有可能本应该用第i -1轮的状态却用的是第i轮的状态。
- 例如:一维状态第i轮对体积为3的物品进行决策,f[7]由 f[4]得来 f[4]正确情况下应为f[i - 1][4], 正序时f[4]此时变成了f[i][4] (循环到第i轮时,f[j]是前i轮中已经决策的物品且背包容量j下的最大值),此时f[4]被污染,逆序时,f[4]还没有在第i轮计算,所以仍然是f[i - 1][4]
- 状态转移方程:f[j] = max(f[j], f[j - v[i]] + w[i])
1.2.2 代码
- 优化一
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; i ++) { //遍历背包数
for (int j = m; j >= 0; j --) { //从1开始遍历体积
if (j < v[i]) { // 当前背包容量不够
// f[i][j] = f[i - 1][j]; // 优化前
f[j] = f[j];
} else { // 取选与不选两者中的最大值
// f[i][j] = max(f[i - 1][j - v[i]] + w[i], f[i - 1][j]); //优化前
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
}
cout << f[m] << endl;
return 0;
}
- 当背包容量大于v[i]时才会更新 循环终止条件可以变更为
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- )
f[j] = max(f[j], f[j - v[i]] + w[i]);
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m; //n 物品数量 m 背包体积
int v[N], w[N]; //v 物品的体积 w 物品的价值
int f[N]; // 最大值
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- ) // 注意当背包容量大于v[i]时才会更新
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
- 处理输入时,一个一个物品,一个一个体积,可以边输入边处理
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m; //n 物品数量 m 背包体积
int f[N]; // 最大值
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) {
int v, w;
cin >> v >> w;
for (int j = m; j >= v; j -- )
f[j] = max(f[j], f[j - v] + w);
}
cout << f[m] << endl;
return 0;
}
2. 完全背包
N种物品,容量为V的背包,每种物品都有无限件可以使用。
第i种物品的体积是vi, 价值是wi。求哪些物品装入背包,可以使这些物品的总体积不超过背包容量,且总价值最大。
2.1 暴力做法
2.1.1 思路
时间复杂度 O ( n 3 ) 时间复杂度O(n^3) 时间复杂度O(n3)
- f [ i ] [ j ] f[i][j] f[i][j]定义:前i个物品,背包容量j下的最优解。
- 每一轮循环对第i个物品决策,选择多少个 ( 0 − ⌊ j / v ⌋ ) (0-\lfloor j/v\rfloor) (0−⌊j/v⌋)第i件物品。
- 每次可选取多个重复物品。
2.1.2 代码
#include <iostream>
using namespace std;
const int N = 10010;
int n, m;
int v[N], w[N];
int f[N][N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; i ++)
for (int j = 0; j <= m; j ++) {
int count = j / v[i];
for (int k = 0; k <= count; k ++) {
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
}
}
cout << f[n][m] << endl;
return 0;
}
2.2 优化做法
2.2.1 思路
时间复杂度
O
(
n
2
)
时间复杂度O(n^2)
时间复杂度O(n2)
上述暴力做法的状态转移方程:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
v
]
+
w
1
,
f
[
i
−
1
]
[
j
−
2
∗
v
]
+
2
∗
w
1
,
.
.
.
,
f
[
i
−
1
]
[
j
−
⌊
j
/
v
⌋
]
∗
v
+
⌊
j
/
v
⌋
]
∗
w
)
f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w_1, f[i - 1][j - 2 * v] + 2 * w_1, ...,f[i - 1][j - \lfloor j/v \rfloor] * v + \lfloor j/v \rfloor] * w)
f[i][j]=max(f[i−1][j],f[i−1][j−v]+w1,f[i−1][j−2∗v]+2∗w1,...,f[i−1][j−⌊j/v⌋]∗v+⌊j/v⌋]∗w)考虑到计算前i个物品的j体积的最优解f[i][j],而前i - 1个物品的最优解f[i - 1][j]在上一轮循环中已经计算完毕,现在只需要判断选择几个第i个种物品得到的价值最大。变量替换一下,将j变成j - v,则有:
f
[
i
]
[
j
−
v
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
v
]
,
f
[
i
−
1
]
[
j
−
2
∗
v
]
+
w
1
,
f
[
i
−
1
]
[
j
−
3
∗
v
]
+
2
∗
w
1
,
.
.
.
,
f
[
i
−
1
]
[
j
−
⌊
(
j
−
v
)
/
v
⌋
]
∗
v
+
⌊
(
j
−
v
)
/
v
⌋
]
∗
w
)
f[i][j - v] = max(f[i - 1][j - v], f[i - 1][j - 2 * v] + w_1, f[i - 1][j - 3 * v] + 2 * w_1, ...,f[i - 1][j - \lfloor (j - v)/v \rfloor] * v + \lfloor (j - v)/v \rfloor] * w)
f[i][j−v]=max(f[i−1][j−v],f[i−1][j−2∗v]+w1,f[i−1][j−3∗v]+2∗w1,...,f[i−1][j−⌊(j−v)/v⌋]∗v+⌊(j−v)/v⌋]∗w)
上述两个方程合并可以得到最新的状态转移方程:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
]
[
j
−
v
]
+
w
)
f[i][j] = max(f[i - 1][j], f[i][j - v] + w)
f[i][j]=max(f[i−1][j],f[i][j−v]+w)
这样优化掉了k这层循环。
- 不用计算每个循环第i个物品个数
- f[i][j]定义:前i个物品j体积的最优解。
- 状态方程:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
]
[
j
−
v
]
+
w
)
f[i][j] = max(f[i - 1][j], f[i][j - v] + w)
f[i][j]=max(f[i−1][j],f[i][j−v]+w)
2.2.2 代码
#include <iostream>
using namespace std;
const int N = 10010;
int n, m;
int v[N], w[N];
int f[N][N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++)
cin >> v[i] >> w[i];
f[0][0] = 0;
for (int i = 1; i <= n; i ++)
for (int j = 0; j <= m; j ++) {
if (j >= v[i])
f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i]); // 选第i个物品
else
f[i][j] = f[i - 1][j]; // 背包容量不够,第i个物品选不了
}
cout << f[n][m] << endl;
return 0;
}
2.3 一维版本
2.3.1 代码一
#include <iostream>
using namespace std;
const int N = 10010;
int n, m;
int v[N], w[N];
int f[N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++)
cin >> v[i] >> w[i];
for (int i = 1; i <= n; i ++)
for (int j = v[i]; j <= m; j ++)
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
2.3.2 代码二
#include <iostream>
using namespace std;
const int N = 10010;
int n, m;
int f[N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
int v, w;
cin >> v >> w;
for (int j = v; j <= m; j ++)
f[j] = max(f[j], f[j - v] + w);
}
cout << f[m] << endl;
return 0;
}
与01背包问题的不同之处递推公式有细微差异。
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]); // 01背包
f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]); // 完全背包问题
3. 多重背包I
N种物品 容量为V的背包。
第i种物品最多s件, 每件体积是v,价值是w。
求哪些物品装入背包,可以使得物品体积总和不超过背包容量,且价值总和最大。
3.1 思路一
- 与0-1背包类似,加上限定条件,枚举物品件数即可。
3.2 代码
#include <iostream>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N], s[N]; //第i种物品的体积 价值 数量
int f[N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++)
cin >> v[i] >> w[i] >> s[i];
for (int i = 1; i <= n; i ++) {
for (int j = m; j >= 0; j -- )
for (int k = 1; k <= s[i]; k ++) {
if (j >= k * v[i])
f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
}
}
cout << f[m] << endl;
return 0;
}
3.3 思路二
拆解。当si = 1时,相当于01背包中的一件物品;当si > 1时,相当于01背包中的多个一件物品。
3.4 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int v, w, s;
int f[N];
int main() {
cin >> n >> m;
while (n --) {
cin >> v >> w >> s;
for (int i = 1; i <= s; i ++) {
for (int j = m; j >= v; j --) {
f[j] = max(f[j], f[j - v] + w);
}
}
}
cout << f[m] << endl;
return 0;
}
4. 多重背包II
描述同上,数据范围扩大。考察多重背包的二进制优化方法。
数据范围
0
<
N
≤
1000
0 < N \leq 1000
0<N≤1000
0
<
V
≤
2000
0 < V \leq 2000
0<V≤2000
0
<
v
i
,
w
i
,
s
i
≤
2000
0 < v_i, w_i, s_i \leq 2000
0<vi,wi,si≤2000
4.1 思路
任意数s,至少选多少个数可以表示成s内的所有数。
比如10, 就可以用1,2,4,3表示任意10以内的数。
a
n
s
=
⌊
l
o
g
2
S
⌋
ans = \lfloor log_2S \rfloor
ans=⌊log2S⌋
所以每个物品可以考虑拆成log(s)份,复杂度降低。转换为0-1背包问题。
4.2 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
int n, m;
int f[N];
struct Good {
int v, w;
};
int main() {
cin >> n >> m;
vector<Good> goods;
for (int i = 0; i <= n; i ++) {
int v, w, s;
cin >> v >> w >> s;
// 拆成log(s) 份
for (int j = 1; j <= s; j *= 2) {
s -= j;
goods.push_back({v * j, w * j});
}
if (s > 0) { // 如果还有剩余
goods.push_back({v * s, w * s});
}
}
for (auto good : goods) {
for (int j = m; j >= good.v; j --) {
f[j] = max(f[j], f[j - good.v] + good.w);
}
}
cout << f[m] << endl;
return 0;
}
5. 分组背包
N组物品和一个容量为V的背包。
每组物品若干个,同一组内的物品最多只能选一个。
每件物品的体积是Vij, 价值是Wij,其中i是组号,j是组内编号。
将哪些物品装入背包,可使得物品总体积不超过背包容量,且总价值最大。
5.1 思路
0-1背包问题的变形。每组物品s个,每组物品的决策有s+1种选择(0~s种)。枚举这些决策即可。
5.2 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N], v[N], w[N];
int main() {
cin >> n >> m;
for (int i = 0; i < n; i ++) {
int s;
cin >> s;
for (int j = 0; j < s; j ++) {
cin >> v[j] >> w[j];
}
for (int j = m; j >= 0; j --) {
for (int k = 0; k < s; k ++) {
if (j >= v[k])
f[j] = max(f[j], f[j - v[k]] + w[k]);
}
}
}
cout << f[m] << endl;
return 0;
}