小S的货船租赁冒险
问题分析
这道题要求在给定的预算 V 元内,从 Q 种不同类型的货船中选择一些船只进行租赁,使得所租用的船只的总载货量最大。每种货船有一定的数量限制 m[i],租赁价格 v[i],以及每艘船的最大载货量 w[i]。
核心问题:在有限的预算和每种货船数量限制下,如何选择租赁的船只组合,使得总载货量最大化。
抽象模型
该问题可以抽象为多重背包问题(Bounded Knapsack Problem):
- 物品:每种货船视为一种物品,每个物品有数量限制。
- 价值:每艘货船的载货量 w[i]。
- 成本:租赁每艘货船的价格 v[i]。
- 目标:在总成本不超过预算 V 的前提下,选择物品使得总价值(载货量)最大。
解题思路
1. 朴素多重背包
最直观的方法是使用多重背包的动态规划算法,时间复杂度为 ,其中 是货船数量的最大值。但是由于每种货船的数量可能较大,直接套用多重背包的做法会导致时间复杂度过高,无法通过所有测试用例。
2. 二进制优化多重背包
为了解决上述问题,我们采用二进制优化的方法,将多重背包问题转化为多个0-1背包问题,从而降低时间复杂度。
二进制优化的原理
对于每种货船的数量 m[i],我们可以将其拆分成若干个物品,其数量为二进制形式。例如:
- 如果 m[i] = 5,我们可以将其拆分为数量为 1, 2, 2 的三个物品()。
- 这样,每个新的物品的数量都是1,我们可以将其视为0-1背包问题的物品。
具体步骤
-
拆分物品:对于每种货船,将其数量按照二进制拆分。例如,对于数量 m[i],拆分成若干个数量为 k 的物品,k 依次为 ,直到总和达到 m[i]。
-
更新动态规划:使用0-1背包的动态规划方法,对于拆分后的每个物品,按照其成本和价值更新DP数组。
3. 动态规划实现
-
状态定义:设定一维DP数组,
dp[c]表示在总成本不超过 c 的情况下,可以获得的最大总载货量。 -
状态转移:对于每个拆分后的物品(成本为
cost = v[i] * count,价值为value = w[i] * count),对于所有的 c 从 V 到 cost,更新dp[c] = max(dp[c], dp[c - cost] + value)。
算法实现
def solution(Q, V, ships):
dp = [0] * (V + 1)
for m_i, v_i, w_i in ships:
k = 1
counts = []
while m_i > 0:
count = min(k, m_i)
counts.append(count)
m_i -= count
k <<= 1 # k *= 2
for count in counts:
cost = v_i * count
value = w_i * count
for c in range(V, cost - 1, -1):
if dp[c - cost] + value > dp[c]:
dp[c] = dp[c - cost] + value
return dp[V]
算法分析
时间复杂度
-
拆分物品:对于每种货船,最多需要拆分 次,总共最多需要处理 个物品。
-
动态规划:对于每个物品,内层循环为从 V 到 cost,总的时间复杂度为 。
-
总体时间复杂度:,其中 N 为拆分后的物品总数,远小于原始的物品总数 Q \times M_{\text{max}}。
空间复杂度
- 使用了一维DP数组,空间复杂度为 。
总结
通过将多重背包问题转换为0-1背包问题,我们有效地降低了时间复杂度,使得算法可以在合理的时间内处理较大的输入规模。这种二进制优化的方法是解决多重背包问题的经典技巧,值得在类似的问题中借鉴和应用。
拓展思考
-
完全背包问题:如果每种货船的数量无限,可以使用完全背包的解法。
-
优化空间:如果需要进一步优化空间,可以考虑滚动数组等技巧。
-
应用场景:该问题的解法在资源分配、预算管理等实际问题中有广泛的应用价值。