Files
python/TangDou/AcWing/BeiBao/9.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

136 lines
3.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##[$AcWing$ $9$. 分组背包问题](https://www.acwing.com/problem/content/description/9/)
### 一、题目描述
有 $N$ 组物品和一个容量是 $V$ 的背包。
每组物品有若干个,**同一组内的物品最多只能选一个**。
每件物品的体积是 $v_{ij}$,价值是 $w_{ij}$,其中 $i$ 是组号,$j$ 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
**输入格式**
第一行有两个整数 $N,V$,用空格隔开,分别表示物品组数和背包容量。
接下来有 $N$ 组数据:
每组数据第一行有一个整数 $S_i$,表示第 $i$ 个物品组的物品数量;
每组数据接下来有 $S_i$ 行,每行有两个整数 $v_{ij},w_{ij}$,用空格隔开,分别表示第 $i$ 个物品组的第 $j$ 个物品的体积和价值;
**输出格式**
输出一个整数,表示最大价值。
**数据范围**
$0<N,V≤100$
$0<S_i≤100$
$0<v_{ij},w_{ij}≤100$
**输入样例**
```cpp {.line-numbers}
3 5
2
1 2
2 4
1
3 4
1
4 5
```
**输出样例**:
```cpp {.line-numbers}
8
```
### 二、解题思路
直接上 **分组背包** 的 **闫氏$DP$分析法**
<center><img src='https://cdn.acwing.com/media/article/image/2021/06/21/55909_0a17f1d9d2-IMG_40E676992253-1.jpeg'></center>
初始状态 :$f[0][0]$
目标状态 :$f[N][M]$
#### 状态表示
$f[i][j]$ 从前 $i$ 组物品中选择且总体积不大于 $j$ 的最大价值
#### 状态转移
针对第 $i$ 组物品,将整个状态划分成 $s[i]+1$ 类:
* ① 第$i$组物品一个都不要:$f[i][j] = f[i-1][j]$
* ② 选第 $i$ 组物品的第一个物品:$f[i][j] = f[i-1][j-v_1]+w_1$
* ③ 选第 $i$ 组物品的第二个物品:$f[i][j] = f[i-1][j-v_2]+w_2$
* ④ 选第 $i$ 组物品的第 $k$ 个物品:$f[i][j] = f[i-1][j-v_k]+w_k$
* ...
$$\large f[i][j]=max(f[i][j], f[i-1][j-v_k]+w_k)) \ k \in [0, 1, 2,...,s_i]$$
#### 初始化
$f[0][0\sim m] = 0$ 表示在选择 `0` 组物品时对于任何体积来讲,其最大价值均为 `0`
### 三、二维数组版本
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N][N], v[N][N], w[N][N], s[N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i]; // 第i个分组中物品个数
for (int j = 1; j <= s[i]; j++)
cin >> v[i][j] >> w[i][j]; // 第i个分组中物品的体积和价值
}
for (int i = 1; i <= n; i++) // 每组
for (int j = 0; j <= m; j++) { // 每个合法体积
f[i][j] = f[i - 1][j]; // 如果一个都不要,那么这一组就相当于白费,给你机会也不中用,继承于i-1
for (int k = 1; k <= s[i]; k++) // 选择第k个
if (j >= v[i][k])
f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]); // 枚举每一个PK一下大小
}
// 输出打表结果
printf("%d", f[n][m]);
return 0;
}
```
### 四、一维数组版本
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];
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++)
for (int j = m; j >= 0; j--)
for (int k = 1; k <= s[i]; k++)
if (j >= v[i][k])
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
printf("%d\n", f[m]);
return 0;
}
```