小S的货船租赁冒险 | 豆包MarsCode AI刷题

49 阅读5分钟

小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,选择最多的货船以达到最大总载货量。

问题分析

  1. 货船数量限制: 每种货船的数量是有限的,不能无限租用。我们需要处理这一限制。

  2. 动态规划模型: 我们将问题转化为一个多重背包问题:

    • 状态表示:dp[j] 表示在预算为 j 时,可以达到的最大总载货量。
    • 状态转移:每种货船类型有数量 m[i],对应的成本 v[i],以及载货量 w[i]。通过将这类问题拆解为若干“01背包问题”来解决。
  3. 问题关键

    • 需要处理“数量限制”的货船,可以通过二进制拆分优化,将多重背包问题转化为若干个 01 背包问题,以降低时间复杂度。

算法设计

采用动态规划结合二进制拆分的方式:

  1. 初始化: 使用一个数组 dp,大小为 V+1,初始化为 0。

  2. 多重背包转换: 对于每种货船 i:

    • 使用二进制拆分,将最多 m[i] 艘货船分解成若干组,每组货船的数量为 1,2,4,…。
    • 每一组货船都相当于一个新的“01背包问题”。
  3. 动态规划更新: 遍历这些分组的货船,按从后往前的方式更新 dp 数组,确保每次更新时使用的是“上一状态”。

  4. 最终结果: 最终,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

代码详解

  1. 二进制拆分

    • 如果货船的数量是 m[i],将其分为若干组,每组的数量为 1,2,4,…,直到剩余的数量小于组的大小。
    • 这样将一个多重背包问题拆解为 O(log⁡m[i]) 个 01 背包问题,降低复杂度。
  2. 动态规划核心

    • 遍历每种货船的分组(按二进制拆分后形成的组)。
    • 使用倒序遍历 dp 数组,确保每次计算时不会重复使用相同的货船。
  3. 时间复杂度

    • 二进制拆分对每种货船的复杂度为 O(log⁡m[i])。
    • 动态规划的复杂度为 O(V⋅log⁡m[i])。
    • 总复杂度为 O(Q⋅V⋅log⁡M),其中 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)。

总结

  • 这种方案通过二进制拆分优化了处理多重背包问题的效率。
  • 在预算约束与数量限制的双重条件下,动态规划结合贪心思想能够很好地解决类似问题。
  • 该方法可应用于资源分配、项目规划等场景,具有较好的通用性和可扩展性。