01背包
问题描述:
有 N N N件物品和一个容量为 V V V的背包。第 i i i件物品的体积是 v [ i ] v[i] v[i],价值是 w [ i ] w[i] w[i]。求解将哪些物品装入背包可使价值总和最大。
思路:
01背包的特点:每件物品可以选择放或者不放。
- 状态表示: f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个物品,容量不超过 j j j的所有方案数中价值的最大值
- 状态计算:不选: f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i - 1][j] f[i][j]=f[i−1][j],选: f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − v [ i ] ) + w [ i ] ) f[i][j] = max(f[i][j], f[i - 1][j - v[i]) + w[i]) f[i][j]=max(f[i][j],f[i−1][j−v[i])+w[i])
Code:
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> v >> w;
for(int j = 1; j <= m; j++)
{
f[i][j] = f[i - 1][j];
if(j >= v) f[i][j] = max(f[i][j], f[i - 1][j - v] + w);
}
}
cout << f[n][m] << endl;
优化:
- 滚动数组优化:
观察到,对于第 i i i层都只和第 i − 1 i-1 i−1层的状态有关。优化如下:
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> v >> w;
for(int j = 1; j <= m; j++)
{
f[i & 1][j] = f[i - 1 & 1][j];
if(j >= v) f[i & 1][j] = max(f[i & 1][j], f[i - 1 & 1][j - v] + w);
}
}
cout << f[n & 1][m] << endl;
- 经典01背包优化:
用 f [ j ] f[j] f[j]来表示背包容量为 j j j的最大价值
在更新状态的时候,我们需要逆序枚举体积(m->v),
为什么要逆序?优化为1维的时候,对于第选 i i i个物品体积为 v v v,则 f [ j ] f[j] f[j]由 f [ j − v ] f[j-v] f[j−v]得来,原本用到的应该是二维的 f [ i − 1 ] [ j − v ] f[i-1][j - v] f[i−1][j−v],如果一维去正序枚举的话用到的就是 f [ i ] [ j − v ] f[i][j - v] f[i][j−v]。如果是逆序得话,就可以避免了。
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> v >> w;
for(int j = m; j >= v; -- j)
f[j] = max(f[j], f[j - v] + w);
}
cout << f[m] << endl;
完全背包
问题描述:
有
N
N
N种物品和一个容量为
V
V
V的背包,每种物品都有无限件可用,第
i
i
i种物品的体积是
v
[
i
]
v[i]
v[i],价值是
w
[
i
]
w[i]
w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思路:
- 状态表示: f [ i ] [ j ] f[i][j] f[i][j]:表示前 i i i个物品,体积不超过 j j j的所有方案中价值的最大值
- 状态计算: f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − k ∗ v ] + k ∗ w ) ( j − k ∗ v ≥ 0 ) f[i][j] = max(f[i][j], f[i - 1][j - k*v] + k*w)(j-k*v\ge 0) f[i][j]=max(f[i][j],f[i−1][j−k∗v]+k∗w)(j−k∗v≥0)
优化一下状态计算:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
v
]
+
w
,
f
[
i
−
1
]
[
j
−
2
v
]
+
2
w
,
⋯
f
[
i
−
1
]
[
k
∗
v
]
+
k
∗
w
)
f[i][j] = max(f[i - 1][j], \quad f[i-1][j -v] + w, f[i-1][j-2v]+2w, \cdots f[i-1][k*v]+k*w)
f[i][j]=max(f[i−1][j],f[i−1][j−v]+w,f[i−1][j−2v]+2w,⋯f[i−1][k∗v]+k∗w)
f
[
i
]
[
j
−
v
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
v
]
,
f
[
i
−
1
]
[
j
−
2
v
]
+
w
,
f
[
i
−
1
]
[
j
−
3
v
]
+
2
w
,
⋯
f
[
i
−
1
]
[
k
∗
v
]
+
(
k
−
1
)
∗
w
)
⋯
f[i][j - v] = max( \qquad \quad \quad f[i - 1][j-v], \quad f[i-1][j -2v] + w, f[i-1][j-3v]+2w, \cdots f[i-1][k*v]+(k-1)*w)\\ \cdots
f[i][j−v]=max(f[i−1][j−v],f[i−1][j−2v]+w,f[i−1][j−3v]+2w,⋯f[i−1][k∗v]+(k−1)∗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)
code:
for(int i = 1 ; i <=n ;i++) {
cin >> v >> w;
for(int j = 0 ; j <=m ;j++)
{
f[i][j] = f[i - 1][j];
if(j - v >= 0) f[i][j] = max(f[i][j],f[i][j - v] + w);
}
}
cout << f[n][m] << endl;
优化:
- 滚动数组优化:
for(int i = 1 ; i <=n ;i++) {
cin >> v >> w;
for(int j = 0 ; j <= m ;j++)
{
f[i & 1][j] = f[i - 1 & 1][j];
if(j - v >= 0) f[i & 1][j] = max(f[i & 1][j],f[i & 1][j - v] + w);
}
}
cout << f[n & 1][m] << endl;
- 经典完全背包优化:
f [ j ] f[j] f[j]表示容量为 j j j的背包的能得到的最大价值
计算:从小到大去计算(与01背包的区别)
for(int i = 1 ; i <=n ;i++) {
cin >> v >> w;
for(int j = v ; j <= m ;j++)
{
f[j] = max(f[j], f[j - v] + w);
}
}
cout << f[m] << endl;
多重背包
问题描述:
有 N N N种物品和一个容量为 V V V的背包。第 i i i种物品最多有 s [ i ] s[i] s[i]件可用,每件体积是 v [ i ] v[i] v[i],价值是 w [ i ] w[i] w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思路:
- 状态表示: f [ j ] f[j] f[j]:体积不超过 j j j的所有方案中价值的最大值
- 状态计算: f [ j ] = m a x ( f [ j ] , f [ j − v ] + w ) f[j] = max(f[j], f[j - v]+ w) f[j]=max(f[j],f[j−v]+w) (对于第 i i i件物品,只需做 s i s_i si次01背包即可)
code:
for(int i = 1; i <= n; ++i)
{
int v, w, s; cin >> v >> w >> s;
for(int k = 1; k <= s; ++k) // 对s次都做一次01背包
for(int j = m; j >= v; --j)
f[j] = max(f[j], f[j - v] + w);
}
cout << f[m];
优化:
- 二进制优化:对于一个数 x x x,我们把它分成 2 0 , 2 1 , 2 2 , ⋯ , 2 t ( 2 t < = x ) , x − 2 t 2^0,2^1, 2^2, \cdots, 2^t(2^t <= x),x - 2^t 20,21,22,⋯,2t(2t<=x),x−2t 。容易发现,对于 0 ∼ x 0 \sim x 0∼x都可以用上述拼出来。那么进行 s i s_i si次的次数就减少到了 l o g 2 s i log_{2}s_i log2si次
cin >> n >> m;
vector<Good>g;
for(int i = 1; i <= n; ++i)
{
int v, w, s; cin >> v >> w >> s;
for(int k = 1; k <= s; k <<= 1)
{
s -= k;
g.push_back({k*v,k*w});
}
if(s > 0) g.push_back({s*v,s*w});
}
for(auto goods : g)
for(int j = m; j >= goods.v; --j)
f[j] = max(f[j], f[j-goods.v] + goods.w);
cout << f[m];
- 单调队列优化
混合背包
有
N
N
N 种物品和一个容量是
V
V
V 的背包。
物品一共有三类:
- 第一类物品只能用1次(01背包);
- 第二类物品可以用无限次(完全背包);
- 第三类物品最多只能用 s i s_i si 次(多重背包);
每种体积是
v
i
v_i
vi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
solution: 对应物品做相应背包即可
题目连接:Acwing 7
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
int f[N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i) {
int v, w, s;
cin >> v >> w >> s;
if(s == 0) {
for(int j = v; j <= m; ++ j)
f[j] = max(f[j], f[j - v] + w);
}else {
if(s == -1) {
for(int j = m; j >= v; -- j) f[j] = max(f[j], f[j - v] + w);
}else {
vector<pair<int, int>> good;
for(int k = 1; k <= s; k <<= 1) {
s -= k;
good.push_back({k * v, k * w});
}
if(s > 0) good.push_back({s * v, s * w});
for(auto Good : good)
for(int j = m; j >= Good.first; -- j) f[j] = max(f[j], f[j - Good.first] + Good.second);
}
}
}
cout << f[m] << "\n";
}
分组背包
问题描述:
题目链接: Acwing 9
有
N
N
N 组物品和一个容量是
V
V
V 的背包。每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是
v
i
,
j
v_{i,j}
vi,j,价值是
w
i
,
j
w_{i,j}
wi,j,其中
i
i
i 是组号,
j
j
j 是组内编号。求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。数据范围均小于100
思路:
对每一组进行01背包
Code:
#include<bits/stdc++.h>
using namespace std;
int n, m;
int f[110], v[110][110], w[110][110], s[110];
int main()
{
cin >> n >> m;
for(int i = 1;i <= n; ++i)
{
cin >> s[i];
for(int j=1;j<=s[i]; ++j)
cin >> v[i][j] >> w[i][j];
}
for(int i = 1; i <= n; ++i)// 枚举组,然后01背包
{
for(int k = m; k >= 0; --k)//每个h对应每组最优解
{
for(int h = 1; h <= s[i]; ++h)
if(k >= v[i][h]) f[k] = max(f[k], f[k - v[i][h] ] + w[i][h]);
}
}
cout << f[m];
return 0;
}
二维费用背包
题目连接:Acwing 8
有
N
N
N 件物品和一个容量是
V
V
V 的背包,背包能承受的最大重量是
M
M
M。
每件物品只能用一次。体积是
v
i
v_i
vi,重量是
m
i
m_i
mi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
思路:
将一维的01背包扩展为二维
Code:
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int f[N][N];
int n, v, m;
int main()
{
cin >> n >> v >> m;
for(int i = 1; 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] << "\n";
}
有依赖的背包
树形DP
- 状态表示: f [ i ] [ j ] f[i][j] f[i][j]表示,考虑以 i i i根节点的子树,选上 i i i,且体积不超过 j j j的所有方案价值的最大值
- 状态计算:将以 i i i为根节点的子树按照体积划分状态,也就是分给根节点下面的体积 f [ u ] [ j ] = m a x ( f [ u ] [ j ] , f [ u ] [ j − k ] + f [ s o n ] [ k ] ) f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]) f[u][j]=max(f[u][j],f[u][j−k]+f[son][k])结合代码理解QAQ
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
vector<int> G[N];
int f[N][N], v[N], w[N], n, m;
void dfs(int u) {
for(int x : G[u]) {
dfs(x); // 从下往上算
for(int j = m - v[u]; j >= 0; -- j) // 枚举一下需要更新的状态
for(int k = 0; k <= j; ++ k) // 枚举一下分给i根节点下面的体积
f[u][j] = max(f[u][j], f[u][j - k] + f[x][k]);
}
// 选上了第u件
for(int j = m; j >= v[u]; -- j) f[u][j] = f[u][j - v[u]] + w[u];
for(int j = 0; j < v[u]; ++ j ) f[u][j] = 0; // 小于就不存在以u为根节点的情况
}
int main()
{
cin >> n >> m;
int root, p;
for(int i = 1; i <= n; ++ i) {
cin >> v[i] >> w[i] >> p;
if(p == -1) root = i;
else G[p].push_back(i);
}
dfs(root);
cout << f[root][m] << "\n";
return 0;
}
背包问题求具体方案
题目链接:Acwing 12
题意:
有
N
N
N 件物品和一个容量是
V
V
V 的背包。每件物品只能使用一次。
第
i
i
i 件物品的体积是
v
i
v_i
vi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是
1
⋯
N
1 \cdots N
1⋯N。
solution:
要求方案,其实就是求一下状态转移的路径。也就是说,对于第
i
i
i件物品,是选了还是没有选。倒着退一下就好
因为本题要求字典序最小的方案,在从
1
∼
n
1 \sim n
1∼n推路径的时候,遇到可以选要先选。所有采用
n
∼
1
n \sim 1
n∼1来进行背包DP
- 状态表示: f [ i ] [ j ] f[i][j] f[i][j], 表示考虑后 i i i件物品,体积不超过 j j j的所有方案的价值的最大值
- 状态转移: 不选: f [ i ] [ j ] = f [ i + 1 ] [ j ] f[i][j] = f[i + 1][j] f[i][j]=f[i+1][j],选 f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i + 1 ] [ j − v ] + w ) f[i][j] = max(f[i][j], f[i + 1][j - v] + w) f[i][j]=max(f[i][j],f[i+1][j−v]+w)
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
int f[N][N];
int v[N], w[N], path[N], cnt;
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++ i) cin >> v[i] >> w[i];
for(int i = n; i >= 1; -- i) {
for(int j = 0; j <= m; ++ j) {
f[i][j] = f[i + 1][j];
if(j >= v[i]) f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
}
}
for(int i = 1, j = m; i <= n; ++ i) {// 能选就先选
if(j >= v[i] && f[i + 1][j - v[i]] + w[i] == f[i][j]) {
path[cnt ++ ] = i;
j -= v[i];
}
}
for(int i = 0; i < cnt; ++ i) cout << path[i] << " ";
}
背包问题求具体方案数
题意:
有
N
N
N 件物品和一个容量是
V
V
V 的背包。每件物品只能使用一次。
第
i
i
i 件物品的体积是
v
i
v_i
vi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模
1
0
9
+
7
10^9 + 7
109+7 的结果。
solution:
01背包模型:
- 状态表示: f [ i ] [ j ] f[i][j] f[i][j]表示考虑前 i i i个物品,体积不超过 j j j的所有方案当中的最大值
- 状态计算: f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i-1][j] f[i][j]=f[i−1][j], f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − v ] + w ) f[i][j] = max(f[i][j], f[i - 1][j - v] + w) f[i][j]=max(f[i][j],f[i−1][j−v]+w)
路径:
- 状态表示: g [ i ] [ j ] g[i][j] g[i][j]表示前 i i i个物品,体积为 j j j且价值大的方案数
- 状态计算: g [ i ] [ j ] = g [ i ] [ j ] + g [ i − 1 ] [ j ] g[i][j] = g[i][j] + g[i - 1][j] g[i][j]=g[i][j]+g[i−1][j], g [ i ] [ j ] = g [ i ] [ j ] + g [ i − 1 ] [ j − v ] ) g[i][j] = g[i][j] + g[i - 1][j - v]) g[i][j]=g[i][j]+g[i−1][j−v])
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int f[N][N], g[N][N];
int n, m, v[N], w[N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; ++ i) {
cin >> v[i] >> w[i];
for(int j = 0; j <= m; ++ j) {
f[i][j] = f[i - 1][j];
if(j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
g[0][0] = 1;
for(int i = 1; i <= n; ++ i) {
for(int j = 0; j <= m; ++ j) {
if(f[i][j] == f[i - 1][j]) {
g[i][j] = (g[i][j] + g[i - 1][j]) % mod;
}
if(j >= v[i] && f[i - 1][j - v[i]] + w[i] == f[i][j]) {
g[i][j] = (g[i][j] + g[i - 1][j - v[i]]) % mod;
}
}
}
int res = 0;
for(int i = 0; i <= m; ++ i) {
if(f[n][i] == f[n][m]) {
res = (res + g[n][i]) % mod;
}
}
cout << res << "\n";
}
背包的例题
相关应用:背包例题