|
2 | 2 |
|
3 | 3 | ## 5. 分组背包问题
|
4 | 4 |
|
5 | | -> **分组背包问题**:有 $n$ 组物品和一个最多能装重量为 $W$ 的背包,第 $i$ 组物品的件数为 $count[i],ドル第 $i$ 组的第 $j$ 个物品重量为 $weight[i][j],ドル价值为 $value[i][j]$。每组物品中最多只能选择 1ドル$ 件物品装入背包。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? |
| 5 | +> **分组背包问题**:有 $n$ 组物品和一个最多能装重量为 $W$ 的背包,第 $i$ 组物品的件数为 $group\underline{}count[i],ドル第 $i$ 组的第 $j$ 个物品重量为 $weight[i][j],ドル价值为 $value[i][j]$。每组物品中最多只能选择 1ドル$ 件物品装入背包。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? |
| 6 | + |
| 7 | +### 5.1 分组背包问题基本思路 |
| 8 | + |
| 9 | +#### 思路 1:动态规划 + 二维基本思路 |
| 10 | + |
| 11 | +###### 1. 划分阶段 |
| 12 | + |
| 13 | +按照物品种类的序号、当前背包的载重上限进行阶段划分。 |
| 14 | + |
| 15 | +###### 2. 定义状态 |
| 16 | + |
| 17 | +定义状态 $dp[i][w]$ 表示为:前 $i$ 组物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。 |
| 18 | + |
| 19 | +状态 $dp[i][w]$ 是一个二维数组,其中第一维代表「当前正在考虑的物品组数」,第二维表示「当前背包的载重上限」,二维数组值表示「可以获得的最大价值」。 |
| 20 | + |
| 21 | +###### 3. 状态转移方程 |
| 22 | + |
| 23 | +由于我们可以不选择 $i - 1$ 组物品中的任何物品,也可以从第 $i - 1$ 组物品的第 0ドル \sim group\underline{}count[i - 1] - 1$ 件物品中随意选择 1ドル$ 件物品,所以状态 $dp[i][w]$ 可能从以下方案中选择最大值: |
| 24 | + |
| 25 | +1. 不选择第 $i - 1$ 组中的任何物品:可以获得的最大价值为 $dp[i - 1][w]$。 |
| 26 | +2. 选择第 $i - 1$ 组物品中第 0ドル$ 件:可以获得的最大价值为 $dp[i - 1][w - weight[i - 1][0]] + value[i - 1][0]$。 |
| 27 | +3. 选择第 $i - 1$ 组物品中第 1ドル$ 件:可以获得的最大价值为 $dp[i - 1][w - weight[i - 1][1]] + value[i - 1][1]$。 |
| 28 | +4. ...... |
| 29 | +5. 选择第 $i - 1$ 组物品中最后 1ドル$ 件:假设 $k = group\underline{}count[i - 1] - 1,ドル则可以获得的最大价值为 $dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k]$。 |
| 30 | + |
| 31 | +则状态转移方程为: |
| 32 | + |
| 33 | +$dp[i][w] = max \lbrace dp[i - 1][w],dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k] \rbrace , \quad 0 \le k \le group\underline{}count[i - 1]$ |
| 34 | + |
| 35 | +###### 4. 初始条件 |
| 36 | + |
| 37 | +- 如果背包载重上限为 0ドル,ドル则无论选取什么物品,可以获得的最大价值一定是 0ドル,ドル即 $dp[i][0] = 0$。 |
| 38 | +- 前 0ドル$ 组物品所能获得的最大价值一定为 0ドル,ドル即 $dp[0][w] = 0$。 |
| 39 | + |
| 40 | +###### 5. 最终结果 |
| 41 | + |
| 42 | +根据我们之前定义的状态,$dp[i][w]$ 表示为:前 $i$ 组物品放入一个最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W],ドル其中 $size$ 为物品的种类数,$W$ 为背包的载重上限。 |
| 43 | + |
| 44 | +#### 思路 1:代码 |
| 45 | + |
| 46 | +```Python |
| 47 | +class Solution: |
| 48 | + # 思路 1:动态规划 + 二维基本思路 |
| 49 | + def groupPackMethod1(self, group_count: [int], weight: [[int]], value: [[int]], W: int): |
| 50 | + size = len(group_count) |
| 51 | + dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)] |
| 52 | + |
| 53 | + # 枚举前 i 组物品 |
| 54 | + for i in range(1, size + 1): |
| 55 | + # 枚举背包装载重量 |
| 56 | + for w in range(W + 1): |
| 57 | + # 枚举第 i - 1 组物品能取个数 |
| 58 | + dp[i][w] = dp[i - 1][w] |
| 59 | + for k in range(group_count[i - 1]): |
| 60 | + if w >= weight[i - 1][k]: |
| 61 | + # dp[i][w] 取所有 dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k] 中最大值 |
| 62 | + dp[i][w] = max(dp[i][w], dp[i - 1][w - weight[i - 1][k]] + value[i - 1][k]) |
| 63 | +``` |
| 64 | + |
| 65 | +#### 思路 1:复杂度分析 |
| 66 | + |
| 67 | +- **时间复杂度**:$O(n \times W \times C),ドル其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$C$ 是每组物品的数量。因为 $n \times C = \sum group\underline{}count[i],ドル所以时间复杂度也可以写成 $O(W \times \sum group\underline{}count[i])$。 |
| 68 | +- **空间复杂度**:$O(n \times W)$。 |
| 69 | + |
| 70 | +### 5.2 分组背包问题滚动数组优化 |
| 71 | + |
| 72 | +#### 思路 2:动态规划 + 滚动数组优化 |
| 73 | + |
| 74 | +###### 1. 划分阶段 |
| 75 | + |
| 76 | +按照当前背包的载重上限进行阶段划分。 |
| 77 | + |
| 78 | +###### 2. 定义状态 |
| 79 | + |
| 80 | +定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。 |
| 81 | + |
| 82 | +###### 3. 状态转移方程 |
| 83 | + |
| 84 | +$dp[w] = max \lbrace dp[w], \quad dp[w - weight[i - 1][k]] + value[i - 1][k] \rbrace ,\quad 0 \le k \le group\underline{}count[i - 1]$ |
| 85 | + |
| 86 | +###### 4. 初始条件 |
| 87 | + |
| 88 | +- 如果背包载重上限为 0ドル,ドル则无论选取什么物品,可以获得的最大价值一定是 0ドル,ドル即 $dp[0] = 0$。 |
| 89 | + |
| 90 | +###### 5. 最终结果 |
| 91 | + |
| 92 | +根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W],ドル其中 $W$ 为背包的载重上限。 |
| 93 | + |
| 94 | +#### 思路 2:代码 |
| 95 | + |
| 96 | +```Python |
| 97 | +class Solution: |
| 98 | + # 思路 2:动态规划 + 滚动数组优化 |
| 99 | + def groupPackMethod2(self, group_count: [int], weight: [[int]], value: [[int]], W: int): |
| 100 | + size = len(group_count) |
| 101 | + dp = [0 for _ in range(W + 1)] |
| 102 | + |
| 103 | + # 枚举前 i 组物品 |
| 104 | + for i in range(1, size + 1): |
| 105 | + # 逆序枚举背包装载重量 |
| 106 | + for w in range(W, -1, -1): |
| 107 | + # 枚举第 i - 1 组物品能取个数 |
| 108 | + for k in range(group_count[i - 1]): |
| 109 | + if w >= weight[i - 1][k]: |
| 110 | + # dp[w] 取所有 dp[w - weight[i - 1][k]] + value[i - 1][k] 中最大值 |
| 111 | + dp[w] = max(dp[w], dp[w - weight[i - 1][k]] + value[i - 1][k]) |
| 112 | + |
| 113 | + return dp[W] |
| 114 | +``` |
| 115 | + |
| 116 | +#### 思路 2:复杂度分析 |
| 117 | + |
| 118 | +- **时间复杂度**:$O(n \times W \times C),ドル其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$C$ 是每组物品的数量。因为 $n \times C = \sum group\underline{}count[i],ドル所以时间复杂度也可以写成 $O(W \times \sum group\underline{}count[i])$。 |
| 119 | +- **空间复杂度**:$O(W)$。 |
6 | 120 |
|
7 | 121 | ## 6. 二维费用背包问题
|
8 | 122 |
|
9 | | -> **二维费用背包问题**: |
| 123 | +> **二维费用背包问题**:有 $n$ 件物品和有一个最多能装重量为 $W$、容量为 $V$ 的背包。第 $i$ 件物品的重量为 $weight[i],ドル体积为 $volume[i],ドル价值为 $value[i],ドル每件物品有且只有 1ドル$ 件。请问在总重量不超过背包载重上限、容量上限的情况下,能装入背包的最大价值是多少? |
| 124 | + |
| 125 | +### 6.1 二维费用背包问题基本思路 |
| 126 | + |
| 127 | +我们可以参考「0-1 背包问题」的状态定义和基本思路,在「0-1 背包问题」基本思路的基础上,增加一个维度用于表示物品的容量。 |
| 128 | + |
| 129 | +#### 思路 1:动态规划 + 三维基本思路 |
| 130 | + |
| 131 | +###### 1. 划分阶段 |
| 132 | + |
| 133 | +按照物品种类的序号、当前背包的载重上限、容量上限进行阶段划分 |
| 134 | + |
| 135 | +###### 2. 定义状态 |
| 136 | + |
| 137 | +定义状态 $dp[i][w][v]$ 为:前 $i$ 件物品放入一个最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。 |
| 138 | + |
| 139 | +###### 3. 状态转移方程 |
| 140 | + |
| 141 | +$dp[i][w][v] = max(dp[i - 1][w][v], dp[i - 1][w - weight[i - 1]][v - volume[i - 1]] + value[i - 1]), \quad 0 \le weight[i - 1] \le w, 0 \le volume[i - 1] \le v$ |
| 142 | + |
| 143 | +> 注意:采用这种「状态定义」和「状态转移方程」,往往会导致内存超出要求限制,所以一般我们会采用「滚动数组」对算法的空间复杂度进行优化。 |
| 144 | + |
| 145 | +###### 4. 初始条件 |
| 146 | + |
| 147 | +- 如果背包载重上限为 0ドル$ 或者容量上限为 0ドル,ドル则无论选取什么物品,可以获得的最大价值一定是 0ドル,ドル即 $dp[i][0][v] = 0,dp[i][w][0] = 0$。 |
| 148 | +- 前 0ドル$ 组物品所能获得的最大价值一定为 0ドル,ドル即 $dp[0][w][v] = 0$。 |
| 149 | + |
| 150 | +###### 5. 最终结果 |
| 151 | + |
| 152 | +根据我们之前定义的状态, $dp[i][w][v]$ 表示为:前 $i$ 件物品放入一个最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。则最终结果为 $dp[size][W][V],ドル其中 $size$ 为物品的种类数,$W$ 为背包的载重上限,$V$ 为背包的容量上限。 |
| 153 | + |
| 154 | +#### 思路 1:代码 |
| 155 | + |
| 156 | +```Python |
| 157 | +class Solution: |
| 158 | + # 思路 1:动态规划 + 三维基本思路 |
| 159 | + def twoDCostPackMethod1(self, weight: [int], volume: [int], value: [int], W: int, V: int): |
| 160 | + size = len(weight) |
| 161 | + dp = [[[0 for _ in range(V + 1)] for _ in range(W + 1)] for _ in range(size + 1)] |
| 162 | + |
| 163 | + # 枚举前 i 组物品 |
| 164 | + for i in range(1, N + 1): |
| 165 | + # 枚举背包装载重量 |
| 166 | + for w in range(W + 1): |
| 167 | + # 枚举背包装载容量 |
| 168 | + for v in range(V + 1): |
| 169 | + # 第 i - 1 件物品装不下 |
| 170 | + if w < weight[i - 1] or v < volume[i - 1]: |
| 171 | + # dp[i][w][v] 取「前 i - 1 件物品装入装载重量为 w、装载容量为 v 的背包中的最大价值」 |
| 172 | + dp[i][w][v] = dp[i - 1][w][v] |
| 173 | + else: |
| 174 | + # dp[i][w][v] 取所有 dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1] 中最大值 |
| 175 | + dp[i][w][v] = max(dp[i - 1][w][v], dp[i - 1][w - weight[i - 1]][v - volume[i - 1]] + value[i - 1]) |
| 176 | + |
| 177 | + return dp[size][W][V] |
| 178 | +``` |
| 179 | + |
| 180 | +#### 思路 1:复杂度分析 |
| 181 | + |
| 182 | +- **时间复杂度**:$O(n \times W \times V),ドル其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$V$ 为背包的容量上限。 |
| 183 | +- **空间复杂度**:$O(n \times W \times V)$。 |
| 184 | + |
| 185 | +### 6.2 二维费用背包问题滚动数组优化 |
| 186 | + |
| 187 | +#### 思路 2:动态规划 + 滚动数组优化 |
| 188 | + |
| 189 | +###### 1. 划分阶段 |
| 190 | + |
| 191 | +按照当前背包的载重上限、容量上限进行阶段划分。 |
| 192 | + |
| 193 | +###### 2. 定义状态 |
| 194 | + |
| 195 | +定义状态 $dp[w][v]$ 表示为:将物品装入最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。 |
| 196 | + |
| 197 | +###### 3. 状态转移方程 |
| 198 | + |
| 199 | +$dp[w][v] = max \lbrace dp[w][v], \quad dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1] \rbrace , \quad 0 \le weight[i - 1] \le w, 0 \le volume[i - 1] \le v$ |
| 200 | + |
| 201 | +###### 4. 初始条件 |
| 202 | + |
| 203 | +- 如果背包载重上限为 0ドル$ 或者容量上限为 0ドル,ドル则无论选取什么物品,可以获得的最大价值一定是 0ドル,ドル即 $dp[w][0] = 0,dp[0][v] = 0$。 |
| 204 | + |
| 205 | +###### 5. 最终结果 |
| 206 | + |
| 207 | +根据我们之前定义的状态, $dp[w][v]$ 表示为:将物品装入最多能装重量为 $w$、容量为 $v$ 的背包中,可以获得的最大价值。则最终结果为 $dp[W][V],ドル其中 $W$ 为背包的载重上限,$V$ 为背包的容量上限。 |
| 208 | + |
| 209 | +#### 思路 2:代码 |
| 210 | + |
| 211 | +```Python |
| 212 | +class Solution: |
| 213 | + # 思路 2:动态规划 + 滚动数组优化 |
| 214 | + def twoDCostPackMethod2(self, weight: [int], volume: [int], value: [int], W: int, V: int): |
| 215 | + size = len(weight) |
| 216 | + dp = [[0 for _ in range(V + 1)] for _ in range(W + 1)] |
| 217 | + |
| 218 | + # 枚举前 i 组物品 |
| 219 | + for i in range(1, N + 1): |
| 220 | + # 逆序枚举背包装载重量 |
| 221 | + for w in range(W, weight[i - 1] - 1, -1): |
| 222 | + # 逆序枚举背包装载容量 |
| 223 | + for v in range(V, volume[i - 1] - 1, -1): |
| 224 | + # dp[w][v] 取所有 dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1] 中最大值 |
| 225 | + dp[w][v] = max(dp[w][v], dp[w - weight[i - 1]][v - volume[i - 1]] + value[i - 1]) |
| 226 | + |
| 227 | + return dp[W][V] |
| 228 | +``` |
| 229 | + |
| 230 | +#### 思路 2:复杂度分析 |
| 231 | + |
| 232 | +- **时间复杂度**:$O(n \times W \times V),ドル其中 $n$ 为物品分组数量,$W$ 为背包的载重上限,$V$ 为背包的容量上限。 |
| 233 | +- **空间复杂度**:$O(W \times V)$。 |
| 234 | + |
| 235 | +## 7. 背包问题变种 |
| 236 | + |
| 237 | +### 7.1 背包问题求方案总数 |
| 238 | + |
| 239 | +> **背包问题求方案数**:在给定背包重量 $W,ドル每件物品重量 $weight[i],ドル物品间相互关系(分组、依赖等)的背包问题中。求解出装满背包或将背包装至某一指定重量的方案总数。 |
| 240 | + |
| 241 | +这种问题就是将原有状态转移方程中的「求最大值」变为「求和」即可。我们以「完全背包问题」求解方案总数为例。 |
| 242 | + |
| 243 | +> **完全背包问题求解方案数**:有 $n$ 种物品和一个最多能装重量为 $W$ 的背包,第 $i$ 种物品的重量为 $weight[i],ドル价值为 $value[i],ドル每种物品数量没有限制。请问装满重量为 $W$ 的背包,一共有多少种方案。 |
| 244 | + |
| 245 | +如果使用二维状态定义,可定义状态 $dp[i][w]$ 为:前 $i$ 种物品放入一个最多能装重量为 $w$ 的背包中,一共有多少种方案。 |
| 246 | + |
| 247 | +则状态转移方程为:$dp[i][w] = sum(dp[i - 1][w], \quad dp[i][w - weight[i - 1]])$。 |
| 248 | + |
| 249 | +如果进行滚动数组优化中,使用一位状态定义,可定义状态 $dp[w]$ 为:装满重量为 $w$ 的背包的方案总数。则状态转移方程为:$dp[w] = sum(dp[w], \quad dp[w - weight[i - 1]])$。 |
| 250 | + |
| 251 | +###### 1. 划分阶段 |
| 252 | + |
| 253 | +按照物品种类的序号、当前背包的载重上限进行阶段划分。 |
| 254 | + |
| 255 | +###### 2. 定义状态 |
| 256 | + |
| 257 | +定义状态 $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中的方案总数。 |
| 258 | + |
| 259 | +###### 3. 状态转移方程 |
| 260 | + |
| 261 | +$dp[w] = sum \lbrace dp[w], \quad dp[w - weight[i - 1]] \rbrace $ |
| 262 | + |
| 263 | +###### 4. 初始条件 |
| 264 | + |
| 265 | +- 如果背包载重上限为 0ドル,ドル则一共有 1ドル$ 种方案(什么也不装),即 $dp[0] = 1$。 |
| 266 | + |
| 267 | +###### 5. 最终结果 |
| 268 | + |
| 269 | +根据我们之前定义的状态, $dp[w]$ 表示为:将物品装入最多能装重量为 $w$ 的背包中的方案总数。则最终结果为 $dp[W],ドル其中 $W$ 为背包的载重上限。 |
| 270 | + |
| 271 | +#### 思路 1:代码 |
| 272 | + |
| 273 | +```Python |
| 274 | + |
| 275 | +``` |
| 276 | + |
| 277 | +#### 思路 1:复杂度分析 |
| 278 | + |
| 279 | +- **时间复杂度**:$O(n \times W),ドル其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 |
| 280 | +- **空间复杂度**:$O(W)$。 |
| 281 | + |
| 282 | +### 7.2 背包问题求最优方案数 |
| 283 | + |
| 284 | +### 7.3 背包问题输出方案 |
10 | 285 |
|
11 | 286 | ## 参考资料
|
12 | 287 |
|
13 | 288 | - 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack)
|
14 | 289 | - 【文章】[背包 DP - OI Wiki](https://oi-wiki.org/dp/knapsack/)
|
| 290 | +- 【文章】[【动态规划/背包问题】分组背包问题](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247487504&idx=1&sn=9ac523ec0ac14c8634a229f8c3f919d7&chksm=fd9cbb0fcaeb32196b80a40e4408f6a7e2651167e0b9e31aa6d7c6109fbc2117340a59db12a1&scene=178&cur_album_id=1751702161341628417#rd) |
0 commit comments