小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
这个问题是经典的多重背包问题(Multiple Knapsack Problem) ,属于动态规划的应用场景。我们需要根据给定预算 VV,选择最多的货船以达到最大总载货量。
问题分析
-
货船数量限制: 每种货船的数量是有限的,不能无限租用。我们需要处理这一限制。
-
动态规划模型: 我们将问题转化为一个多重背包问题:
- 状态表示:dp[j] 表示在预算为 j 时,可以达到的最大总载货量。
- 状态转移:每种货船类型有数量 m[i],对应的成本 v[i],以及载货量 w[i]。通过将这类问题拆解为若干“01背包问题”来解决。
-
问题关键:
- 需要处理“数量限制”的货船,可以通过二进制拆分优化,将多重背包问题转化为若干个 01 背包问题,以降低时间复杂度。
算法设计
采用动态规划结合二进制拆分的方式:
-
初始化: 使用一个数组
dp,大小为 V+1,初始化为 0。 -
多重背包转换: 对于每种货船 i:
- 使用二进制拆分,将最多 m[i] 艘货船分解成若干组,每组货船的数量为 1,2,4,…。
- 每一组货船都相当于一个新的“01背包问题”。
-
动态规划更新: 遍历这些分组的货船,按从后往前的方式更新
dp数组,确保每次更新时使用的是“上一状态”。 -
最终结果: 最终,dp[V] 就是预算为 V 时能达到的最大总载货量。
代码实现
以下是 Python 的实现代码:
def max_cargo(Q, V, ships):
# 初始化 dp 数组,表示在预算 j 下的最大载货量
dp = [0] * (V + 1)
# 遍历每种货船类型
for m, v, w in ships:
# 对当前货船进行二进制拆分
amount = m # 当前货船的总数量
k = 1
while amount > 0:
current_count = min(k, amount) # 当前分组的数量(1, 2, 4, ...)
cost = current_count * v # 当前分组的总租金
weight = current_count * w # 当前分组的总载货量
# 从后往前更新 dp 数组
for j in range(V, cost - 1, -1):
dp[j] = max(dp[j], dp[j - cost] + weight)
amount -= current_count # 剩余货船数量减少
k *= 2 # 下一个二进制分组
# 返回结果
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
代码详解
-
二进制拆分:
- 如果货船的数量是 m[i],将其分为若干组,每组的数量为 1,2,4,…,直到剩余的数量小于组的大小。
- 这样将一个多重背包问题拆解为 O(logm[i]) 个 01 背包问题,降低复杂度。
-
动态规划核心:
- 遍历每种货船的分组(按二进制拆分后形成的组)。
- 使用倒序遍历 dp 数组,确保每次计算时不会重复使用相同的货船。
-
时间复杂度:
- 二进制拆分对每种货船的复杂度为 O(logm[i])。
- 动态规划的复杂度为 O(V⋅logm[i])。
- 总复杂度为 O(Q⋅V⋅logM),其中 M 是所有货船的最大数量。
测试结果
- 样例1: 输入:
Q = 2, V = 10, ships = [[2, 3, 2], [3, 2, 10]]
输出:32
解释:租用两艘第二种货船(载货量 10,每艘成本 2)和一艘第一种货船(载货量 2,成本 3)。 - 样例2: 输入:
Q = 3, V = 50, ships = [[5, 10, 20], [2, 20, 30], [3, 15, 25]]
输出:100 - 样例5: 输入:
Q = 2, V = 300, ships = [[100, 1, 1], [50, 5, 10]]
输出:550
解释:租用 100 艘第一种货船(总载货量 100)和 50 艘第二种货船(总载货量 500)。
总结
- 这种方案通过二进制拆分优化了处理多重背包问题的效率。
- 在预算约束与数量限制的双重条件下,动态规划结合贪心思想能够很好地解决类似问题。
- 该方法可应用于资源分配、项目规划等场景,具有较好的通用性和可扩展性。