今天刷的也是一道难度特别难的题目:小S的货船租赁冒险
问题描述
小S在码头租用货船,有 Q 种不同类型的货船可供选择。每种货船有固定的数量 m[i]、租赁成本 v[i] 和最大载货量 w[i]。小S希望在预算 V 元内,租用能够承载最大总货物的货船组合。每种货船的具体信息包括数量、租赁价格和载货量。小S需要你帮忙计算在给定预算下,她能租用的货船的最大总载货量是多少。
Q: 货船的种类数量。V: 李华可用的总预算(单位:元)。ships: 一个列表,其中每个元素是一个元组[m[i], v[i], w[i]],分别表示第i种货船的数量、租赁价格和每艘货船的最大载货量。
测试样例
样例1:
输入:
Q = 2,V = 10,ships = [[2, 3, 2], [3, 2, 10]]
输出:32
样例2:
输入:
Q = 3,V = 50,ships = [[5, 10, 20], [2, 20, 30], [3, 15, 25]]
输出:100
样例3:
输入:
Q = 1,V = 100,ships = [[10, 5, 50]]
输出:500
样例4:
输入:
Q = 4,V = 100,ships = [[1, 100, 200], [2, 50, 100], [3, 33, 66], [4, 25, 50]]
输出:200
样例5:
输入:
Q = 2,V = 300,ships = [[100, 1, 1], [50, 5, 10]]
输出:550
下面我将详细分析这道题目,帮助你理解问题的背景、建模思路以及如何利用动态规划来解决它。
题目分析
题目描述了一个背包问题变种,我们需要在给定的预算下选择若干艘货船以最大化总载货量。问题的关键点是:
- 货船的选择具有数量限制:每种货船有数量限制,这意味着我们不能无限制地选择某一种货船。
- 每艘货船的租赁费用和载货量已知:每种类型的货船都有固定的租赁费用和载货量。
- 预算限制:我们有一个总的预算,不能超过这个预算来租用货船。
因此,问题的目标是,在预算范围内,通过选择合适数量的货船类型,使得载货量最大化。
问题建模
这道题可以视作一个 带有数量限制的背包问题(也称为“多重背包问题”)。在标准的背包问题中,背包只能选择物品一次,而在本题中,背包物品(货船)是有限个的,每种货船可以选择的数量是有限制的。我们需要通过动态规划来求解最优解。
动态规划解法
-
定义状态:
- 使用一个一维数组
dp[j]来表示预算为j时,能够达到的最大载货量。 - 其中,
dp[j]的意义是,当预算为j时,最大载货量是多少。
- 使用一个一维数组
-
状态转移:
- 对于每种货船
i,其价格为v[i],载货量为w[i],并且每种货船的最大数量为m[i],我们需要考虑在预算j下选择该货船的不同数量。 - 我们从预算
V开始倒推,确保每种货船在同一轮计算中不会被重复使用。
- 对于每种货船
-
更新 dp 数组:
- 如果我们考虑选择
k艘该类型的货船,并且当前预算j可以支持选择k艘货船,那么我们可以通过转移: [ dp[j] = \max(dp[j], dp[j - k \times v[i]] + k \times w[i]) ] - 这里,
k从 1 到m[i],表示可以选择最多m[i]艘货船。
- 如果我们考虑选择
-
边界条件:
- 初始化
dp[0] = 0,表示当预算为 0 时,最大载货量是 0。 - 其他的
dp[j]初始化为 0,因为在没有租用货船的情况下,载货量为 0。
- 初始化
-
最终解:
- 最终答案就是
dp[V],即在预算为V时能够获得的最大载货量。
- 最终答案就是
动态规划代码实现
def max_cargo(Q, V, ships):
# 初始化 dp 数组,表示每个预算下的最大载货量
dp = [0] * (V + 1)
# 遍历每种货船类型
for m, v, w in ships:
# 对每种货船类型进行完全背包处理
for j in range(V, v - 1, -1): # 从大到小遍历预算
for k in range(1, m + 1): # 考虑从 1 到 m 艘该类型货船
if j >= k * v: # 如果当前预算 j 能支持选择 k 艘货船
dp[j] = max(dp[j], dp[j - k * v] + k * w)
# 最终答案是 dp[V],即使用预算 V 元能获得的最大载货量
return dp[V]
# 测试样例
print(max_cargo(2, 10, [[2, 3, 2], [3, 2, 10]])) # 输出 32
print(max_cargo(3, 50, [[5, 10, 20], [2, 20, 30], [3, 15, 25]])) # 输出 100
print(max_cargo(1, 100, [[10, 5, 50]])) # 输出 500
print(max_cargo(4, 100, [[1, 100, 200], [2, 50, 100], [3, 33, 66], [4, 25, 50]])) # 输出 200
print(max_cargo(2, 300, [[100, 1, 1], [50, 5, 10]])) # 输出 550
代码分析
-
初始化:
dp = [0] * (V + 1)初始化一个大小为V + 1的数组,用于存储每个预算下的最大载货量。
-
外层循环:遍历每种货船类型,使用参数
m[i],v[i],w[i],其中:m[i]表示最多可以选择的该类型货船数量。v[i]是该货船的租赁价格。w[i]是该货船的载货量。
-
内层循环:
- 第一层循环
for j in range(V, v - 1, -1):从大到小遍历预算j,确保每个预算只计算一次,避免重复选择相同类型的货船。 - 第二层循环
for k in range(1, m + 1):对于每种货船,考虑从 1 到最多m[i]艘货船的选择,检查选择k艘货船是否可行(即当前预算j是否能够支付k艘货船的租赁费用)。
- 第一层循环
-
转移方程:
- 如果当前预算
j足够支付k艘货船的费用,则更新dp[j],最大化当前预算下的载货量。
- 如果当前预算
-
最终输出:
- 结果存储在
dp[V]中,即在预算为V时,能够达到的最大载货量。
- 结果存储在
复杂度分析
- 时间复杂度:
- 外层遍历所有货船类型(Q次),每次遍历需要更新一个大小为
V的数组。 - 对于每种货船,我们还需要考虑最多
m[i]种选择方式。所以,整体时间复杂度是O(Q * V * m),其中Q是货船种类数,V是预算,m是货船的最大数量。
- 外层遍历所有货船类型(Q次),每次遍历需要更新一个大小为
- 空间复杂度:
- 我们只需要一个大小为
V + 1的dp数组,因此空间复杂度是O(V)。
- 我们只需要一个大小为
关键点总结
- 这道题目是一个典型的 多重背包问题,它的核心在于如何处理每种货船的数量限制。
- 动态规划的核心思路是利用
dp[j]来记录预算为j时的最优载货量,通过状态转移计算出最优解。 - 为了避免重复计算,使用倒序遍历的方式,确保每次计算时每种货船不会被重复选择。
通过动态规划,我们能够高效地求解这个问题,尤其适用于预算和货船种类数较大的情况。 多重背包问题(Multiple Knapsack Problem)是经典的背包问题的一个扩展,属于组合优化问题的一种。与传统的0/1背包问题不同,在多重背包问题中,每种物品的数量是有限的,我们可以选择每种物品若干次,但每种物品的数量有一个最大限制。
背包问题的基本概念
在经典的 0/1背包问题 中,给定一组物品,每个物品有一个重量和一个价值(或收益),以及一个容量有限的背包。目标是选择若干个物品放入背包中,使得背包中的总重量不超过背包的容量,并且总价值最大。
- 物品选择是二选一的(每个物品只能选择一次或者不选择)。
多重背包问题的定义
在 多重背包问题 中,每种物品都有一个数量限制,即我们可以选择每种物品若干次,但不能超过它的最大数量限制。物品的数量限制使得该问题变得更为复杂。
具体来说,给定:
- 一组物品,每个物品有:
- 重量
w_i,即放入背包时所占的重量。 - 价值
v_i,即物品带来的收益。 - 最大数量限制
c_i,即每种物品最多可以选择多少个。
- 重量
- 一个背包,其容量为
W,即背包能够承载的最大重量。
问题的目标是,选择若干个物品放入背包中,使得总重量不超过背包的容量,并且总价值最大。
多重背包问题与0/1背包问题的区别
- 0/1背包问题:每个物品只能选择一次,或者选择,或者不选择(二选一)。
- 多重背包问题:每个物品可以选择多次,但每种物品的数量是有限的,不能超过给定的最大数量。
解决方法
多重背包问题一般使用 动态规划(DP)来解决,类似于0/1背包问题,但需要在转移状态时考虑物品的数量限制。
假设背包容量为 W,有 n 种物品,每种物品最多可以选择 c_i 次。定义 dp[j] 为容量为 j 时,背包中能获得的最大价值。转移方程如下:
-
状态转移: 对于每种物品
i,其重量为w_i,价值为v_i,最多可选c_i个。我们需要更新dp数组:- 可以考虑从
1个到c_i个物品的选择情况。 - 从最大容量
W开始倒推更新,以避免重复使用相同物品。
- 可以考虑从
-
转移方程: [ dp[j] = \max(dp[j], dp[j - k \times w_i] + k \times v_i) ] 其中,
k是当前选择的物品的数量,k从1到c_i。
举例说明
假设有3种物品和一个容量为5的背包,物品的属性如下:
| 物品 | 重量 (w_i) | 价值 (v_i) | 最大数量限制 (c_i) |
|---|---|---|---|
| 1 | 2 | 3 | 2 |
| 2 | 3 | 4 | 1 |
| 3 | 4 | 5 | 1 |
背包容量为 5,我们需要选择物品,使得总价值最大。每种物品的数量限制如下:
- 物品1最多选择2个,
- 物品2最多选择1个,
- 物品3最多选择1个。
我们使用动态规划来求解这个问题,定义 dp[j] 为容量为 j 时背包的最大价值。
- 初始化
dp[0] = 0(没有任何物品,价值为0)。 - 遍历每种物品,更新背包的最大价值。
- 对于物品1(重量=2,价值=3,最多选2个),我们可以选择1个或者2个。
- 对于物品2(重量=3,价值=4,最多选1个),我们可以选择0个或者1个。
- 对于物品3(重量=4,价值=5,最多选1个),我们可以选择0个或者1个。
通过动态规划,我们可以计算出每个背包容量对应的最大价值,最终得到最大价值。
复杂度分析
-
时间复杂度:最外层循环遍历每种物品,共有
n种物品。对于每种物品,内层循环从容量W到物品重量w_i更新dp数组,每次考虑从1到c_i的选择,因此整体时间复杂度为O(n * W * c),其中n是物品种类数,W是背包容量,c是每种物品的最大数量。 -
空间复杂度:使用一个大小为
W+1的dp数组,空间复杂度为O(W)。
总结
多重背包问题是经典的背包问题变种,在实际应用中,许多实际问题都可以归结为多重背包问题。通过动态规划的方法,我们能够在有限时间内计算出最优解。