# 背包问题
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。
***
***
## 01背包
- 有 $N$ 件物品和一个容量是 $V$ 的背包。**每件物品只能使用一次**
- 第 $i$ 件物品的体积是 v$i$,价值是 w$i$.
***
***
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V;
cin >> n >> V;
vector<int> v(n + 1, 0), w(n + 1, 0);
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
}
vector<vector<int>> dp(n + 1, vector<int>(V + 1, 0));
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= V; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= v[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
}
}
cout << dp[n][V];
return 0;
}
```
##### 每个人对于背包中每种量的表达方式不一样仅供参考
$v[i] 表示物品 i 的重量$
$w[i] 表示物品 i 的价值$
$dp[i][j] 表示前i个数重量为j时的最大价值$
$背包作为动规的一种它也有着其独特的转移公式$
$dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i])$
$01背包每种物品只能选一次所以应当从大到小转移$
$这样每一次转移时就不会出现一个物品被选择了多次的情况发生$
```cpp
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= V; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= v[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
}
}
```
$因为01背包每次调用的都是它前面的最大价值,所以我们可以把dp的前一位去掉$
$dp[j] 表示重量为j时的最大价值$
$我们就可以得到转移公式$
$dp[j] = max(dp[j], dp[j - v[i]] + w[i])$
以及简化代码
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, V, v, w;
cin >> n >> V;
vector<int> dp(V + 1, 0);
for (int i = 1; i <= n; i++) {
cin >> v >> w;
for (int j = V; j >= v; j--) {
dp[j] = max(dp[j], dp[j - v] + w);
}
}
cout << dp[V] << '\n';
return 0;
}
```
_01背包完结_
***
***
***
## 完全背包
- _有 $N$ 种物品和一个容量是 $V$ 的背包,**每种物品都有无限件可用。**_
- _第 $i$ 种物品的体积是 v$i$ ,价值是 w$i$_
***
***
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V, w, v;
cin >> n >> V;
vector<int> dp(V + 1, 0);
for (int i = 0; i < n; i++) {
cin >> v >> w;
for (int j = v; j <= V; j++) {
dp[j] = max(dp[j], dp[j - v] + w);
}
}
cout << dp[V];
return 0;
}
```
_相信大家已经发现了,_
_完全背包与01背包唯一的区别就是_
_第二层循环由从大到小变成了从小到大_
因为完全背包每一种物品的个数都是无限多个,所以它可以由自己得到自己
_完全背包完结_
***
***
***
## 多重背包
- 有 $N$ 种物品和一个容量是 $V$ 的背包。
- 第 $i$ 种物品最多有 s$i$件,每件体积是 v$i$,价值是 w$i$。
***
***
一种简单粗暴的方式是将 $i$ 个物品直接拆解为$i$种物品 _01背包_ 暴力求解,但这种方式太过暴力,时间复杂度过高,因此我们可以考虑用二进制存储,再用
_01背包_
的方式求解
——我们可以考虑这样储存
假设有$10$个物品
那我们可以将它分解为$(2^0 + 2^1 + 2^2 + 3)$
再采用
_01背包_
的方法
_代码如下:_
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V, s;
cin >> n >> V;
vector<int> v, w;
v.push_back(0);
w.push_back(0);
for (int i = 1; i <= n; i++) {
int v1, w1;
cin >> v1 >> w1 >> s;
int k = 1, s1 = s;
while (s >= k) {
s -= k;
v.push_back(v1*k);
w.push_back(w1*k);
k *= 2;
}
if (s > 0) {
v.push_back(v1*s);
w.push_back(w1*s);
}
}
vector<int> dp(v.size(), 0);
for (int i = 0; i < v.size(); i++) {
for (int j = V; j >= v[i]; j--) {
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[V];
return 0;
}
```
那为什么我们可以用二进制分解呢?
$10$的二进制表示为$(1010)$
那么$(100) + (10) + (1)$ 再加上 $3$ 的任意组合
是不是就可以将它的所有可能性表示完了呢?
_多重背包完_
***
***
***
## 混合背包
_物品一共有三类:_
- _第一类物品只能用1次(01背包)_;
- _第二类物品可以用无限次(完全背包);_
- _第三类物品最多只能用 s $i$ 次(多重背包);_
***
***
这一题就不过多赘述了,把前面的套上就好了。
_代码如下:_
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V, w, v, s;
cin >> n >> V;
vector<int> dp(V + 1, 0);
for (int i = 0; i < n; i++) {
cin >> v >> w >> s;
if (s == 0) {
for (int j = v; j <= V; j++) {
dp[j] = max(dp[j], dp[j - v] + w);
}
} else if (s == -1) {
for (int j = V; j >= v; j--) {
dp[j] = max(dp[j], dp[j - v] + w);
}
} else {
int v1 = v, w1 = w, sum = 0;
for (int i = 1; s > 0; i * = 2) {
int k = min(i, s);
v1 = v*k;
w1 = w*k;
for (int j = V; j >= v1; j--) {
dp[j] = max(dp[j], dp[j - v1] + w1);
}
s -= i;
}
}
}
cout << dp[V] << '\n';
return 0;
}
```
_混合背包完_
***
***
***
## 二维费用背包
- _有 $N$ 件物品和一个 容量是 $V$ 的背包,背包能承受的最大重量是 $M$。_
- _每件物品只能用一次。体积是 v$i$ ,重量是m$i$ ,价值是 w$i$。_
_求解将哪些物品装入背包,可使物品 总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。_
_输出最大价值。_
***
***
#### 这一题又多了一个量,我们同样用动规的方式解决。
思考状态和转移方程
_我们可以得到:_
$dp[i][j] = max(dp[i][j], dp[i - v[i]][j - m[i]] + w[i])$
$v[i]$表示体积,$m[i]$表示重量
_代码如下:_
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V, M;
cin >> n >> V >> M;
vector<vector<int>> dp(V + 1, vector<int>(M + 1, 0));
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--) {
dp[j][k] = max(dp[j][k], dp[j - v][k - m] + w);
}
}
}
cout << dp[V][M];
return 0;
}
```
_二维费用背包完_
***
***
***
## 分组背包
- _有 $N$ 组物品和一个容量是 $V$ 的背包。_
- _每组物品有若干个,同一组内的物品最多只能选一个。 每件物品的体积是 **V**$_i$
$_j$ ,价值是 **W**$_i$
$_j$ ,其中
$i$ 是组号,$j$ 是组内编号。_
_求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。_
_输出最大价值。_
***
***
因为
_“同一组内的物品最多只能选一个”_
所以每一种状态最多只能存在同组中的一个物品,
因为每一种物品只有一个,
_所以考虑 :_
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, V;
cin >> n >> V;
vector<int> dp(V + 1, 0);
for (int i = 1; i <= n; i++) {
int s;
cin >> s;
vector<int> v(s + 1, 0), w(s + 1, 0);
for (int j = 1; j <= s; j++) cin >> v[j] >> w[j];
for (int j = V; j >= 0; j--) {
for (int k = 1; k <= s; k++) {
if (j >= v[k]) dp[j] = max(dp[j], dp[j - v[k]] + w[k]);
}
}
}
cout << dp[V];
return 0;
}
```
#### 让同组的物品不可能存在于同一种状态里
_分组背包完_
***
***
***
## 有依赖的背包
_有 $N$ 个物品和一个容量是 $V$ 的背包。_
**物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。**
![logo](https://img0.baidu.com/it/u=225831478,977589636&fm=253&fmt=auto&app=138&f=JPEG?w=350&h=248)
- _每件物品的编号是 $i$,体积是v$_i$ ,价值是 w$_i$ ,依赖的父节点编号是 p$_i$ 。物品的下标范围是 $1…N。$_
- _$P_i = -1$,表示根节点。 数据保证所有物品构成一棵树。_
_求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。_
_输出最大价值。_
***
***
1. 首先对于输入,设置数组g存储每个点的子节点
2. 考虑将背包与广搜结合,从根节点开始遍历整棵树
3. 考虑采用完全背包
4. 思考转移公式与状态
_——因此我们可以得到 :_
$dp[u][i]=max(dp[u][i],dp[u][i-j]+dp[x][j])$
>u表示当前子树的根节点
>***
>i表示重量
>***
>j表示当前遍历的重量
>***
>x表示当前遍历的子节点
>***
> dp[u][i]表示 以节点u为根节点的子树长度恰好为$i$的最大价值
>***
_代码如下 :_
```cpp
#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> g;
vector<vector<int>> dp;
vector<int> v, w;
int n, V;
void dfs(int u) {
dp[u][0] = 0;
dp[u][v[u]] = w[u];
for (int x : g[u]) {
dfs(x);
for (int i = V; i >= 0; i--) {
for (int j = 0; j <= V; j++) {
if ((i - j) > 0 && dp[u][i - j] != -1) dp[u][i] = max(dp[u][i], dp[u][i - j] + dp[x][j]);
}
}
}
}
int main() {
cin >> n >> V;
g = vector<vector<int>>(n + 1, vector<int>());
dp = vector<vector<int>>(n + 1, vector<int>(V + 1, -1));
int r;
v = vector<int>(n + 1);
w = vector<int>(n + 1);
for (int i = 1; i <= n; i++) {
int p;
cin >> v[i] >> w[i] >> p;
if (p != -1) g[p].push_back(i);
else r = i;
}
dfs(r);
int sum = 0;
for (int x : dp[r]) {
if (x > sum) sum = x;
}
cout << sum;
return 0;
}
```
_——有依赖背包完_
***