背包问题本质: 将"代价"变成二维的(重量+体积)
动态规划:
# 8 二维费用的背包问题
二维费用 + 01/完全/多重/分组 背包
本质: 将“代价”变成二维的(重量+体积)
动态规划:
![[image-20250812131030120.p- 状态计算:
- f[i, j] = f[i-1, j] + f[i-1, j-k*vi] (k=1, 2, 3, ...)
- 由于是完全背包,所以可以进行"递推优化"
-
- final: f[i, j] = f[i-1, j] + f[i, j-v]
0]]
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | #include <iostream>
using namespace std;
const int N = 110;
int n, V, M;
int f[N][N];
int main()
{
cin >> n >> V >> M;
for (int i = 0; i < n; i ++ ) // 物品
{
int v, m, w;
cin >> v >> m >> w;
for (int j = V; j >= v; j -- ) // 体积
for (int k = M; k >= m; k -- ) // 重量
f[j][k] = max(f[j][k], f[j - v][k - m] + w);
}
cout << f[V][M] << endl;
return 0;
}
|
一般来说,有几个“变量” 就 有几个 for
278 数字组合
给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案:
方案数 很简单,就是求解属性变成count了
思路:
- M: 背包容量
- 把每个数看成一个物品,\(A_i\) 看成是体积
- 目标: 求出总体积恰好是M的方案数
动态规划
- 状态表示:
f[i, j]
- 集合: 所有只从前 i 个物品中选,且总体积恰好为 j 的方案的集合
- 属性: count
- 状态计算:
f[i, j] = f[i-1, j] + f[i-1, j-Ai]

C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | // 朴素 二维
#include<iostream>
using namespace std;
const int maxn = 1e4+10;
int a[maxn];
int f[maxn][maxn];
int n, m;
int main()
{
cin >> n >> m;
for (int i=1; i<=n; i++) cin >> a[i];
// 初始化: 什么都不选 -> 恒为0 -> 也是一种策略
for (int i=0; i<=n; i++) f[i][0] = 1;
for (int i=1; i<=n; i++)
{
for (int j=1; j<=m; j++)
{
f[i][j] = f[i-1][j];
if (j >= a[i]) // 因为是从i-1转移而来, 因此1D优化时, j从大到小
f[i][j] = f[i][j] + f[i-1][j-a[i]];
}
}
cout << f[n][m] <<endl;
return 0;
}
|
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 | // 一维 优化
#include<iostream>
using namespace std;
const int maxn = 1e4+10;
int a[maxn];
int f[maxn];
int n, m;
int main()
{
cin >> n >> m;
for (int i=1; i<=n; i++) cin >> a[i];
// 初始化: 什么都不选 -> 恒为0 -> 也是一种策略
for (int i=0; i<=n; i++) f[0] = 1;
for (int i=1; i<=n; i++)
{
for (int j=m; j>=a[i]; j--)
{
f[j] = f[j] + f[j-a[i]];
}
}
cout << f[m] <<endl;
return 0;
}
|
1019 庆功会
多重背包
暴力版复杂度: n x m x s = \(3 \times 10^7\). Ok!!!
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 | #include<bits/stdc++.h>
using namespace std;
const int N = 6010;
int n, m;
int f[N]; // 动态规划
int main()
{
cin >> n >> m; // m: 金额上限
for (int i=0; i<n; i++) // 物品
{
int v, w, s;
cin >> v >> w >> s;
for (int j=m; j>=0; j--) // 金额
{
for (int k=0; k<=s && k*v<=j; k++) // 当前物品选多少件
{
// 注意: k<=s && k*v <= j
f[j] = max(f[j], f[j-k*v] + k*w);
}
}
}
cout << f[m] << endl;
return 0;
}
|
1023 买书
完全背包
动态规划
- 状态表示:
f[i, j]
- 集合: 所有只从前 i 类物品中选,且总体积恰好为 j 的方案的集合
- 属性: count
- 状态计算:
f[i, j] = f[i-1, j] + f[i-1, j-k*vi] (k=1, 2, 3, ...)
- 由于是完全背包,所以可以进行“递推优化”

- final:
f[i, j] = f[i-1, j] + f[i, j-v]
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | // 二维朴素版
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[5] = {0, 10, 20, 50, 100};
int f[5][N];
int n;
int main()
{
cin >> n;
f[0][0] = 1; // 这也是一种解, 初始化
for (int i=1; i<=4; i++)
{
for (int j=0; j<=n; j++)
{
f[i][j] = f[i-1][j];
if (j >= a[i]) f[i][j] += f[i][j-a[i]];
}
}
cout << f[4][n] << endl;
return 0;
}
|
注意⚠️: 当空间优化成1维后, 只有 完全背包问题 的体积是 从小到大 扫描的
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | // 一维优化版
#include <iostream>
using namespace std;
const int N = 1010;
int n;
int v[4] = {10, 20, 50, 100};
int f[N];
int main()
{
cin >> n;
f[0] = 1;
for (int i = 0; i < 4; i ++ )
for (int j = v[i]; j <= n; j ++ )
f[j] += f[j - v[i]];
cout << f[n] << endl;
return 0;
}
|
转移原则: 从i-1层转移而来,就要从大到小循环;从i层转移而来,要从小到大循环