伴学笔记之题解6 | 豆包MarsCode AI刷题

54 阅读3分钟

题解:多重背包问题

83 小S的货船租赁冒险

问题分析

这是一道经典的多重背包问题。小S希望在预算 V 内,选择若干种货船,以使总载货量最大化。对于每种货船,其数量有限,这种限制增加了问题的复杂性。

解决此问题的关键是使用动态规划,构建一个容量为 V 的背包,逐步将每种货船的选择纳入决策。


动态规划思路

我们可以用一个数组 dp 来表示在不同预算情况下的最大载货量,其中:

  • dp[j] 表示在预算为 jj 时,小S可以获得的最大载货量。

状态转移方程: 对于第 ii 种货船,租赁成本为 v[i],最大载货量为 w[i],数量为 m[i],我们需要考虑是否选择这一种货船。决策时需满足预算 jv[i]j \geq v[i]

  • 对于货船 i 的每一艘,我们逐步分配给当前预算 j: dp[j]=max(dp[j],dp[jkv[i]]+kw[i])dp[j] = \max(dp[j], dp[j - k \cdot v[i]] + k \cdot w[i]) 其中 k 表示选择的船数量,范围是 0km[i]0 \leq k \leq m[i]

优化处理: 由于直接枚举 kk 会导致时间复杂度较高,可以采用二进制分组优化。将货船的数量 m[i] 转化为若干虚拟物品,避免直接枚举。


算法步骤

  1. 初始化

    • 创建一个数组 dp,长度为 V+1V + 1,初始值为 0,表示初始预算下的最大载货量。
  2. 二进制分组

    • 对于每种货船 i,将其数量 m[i] 分解为若干件物品(数量为 1,2,4,…,直至不超过 m[i]),并将每件物品视为独立的选择。
  3. 动态规划转移

    • 遍历所有虚拟物品,根据租赁成本和载货量更新 dp 数组。
  4. 结果输出

    • 输出 dp[V],即在预算 VV 下的最大载货量。

示例代码

def max_cargo(Q, V, ships):
    # 初始化 DP 数组
    dp = [0] * (V + 1)
    
    # 遍历每种货船
    for m, v, w in ships:
        # 二进制分组
        k = 1
        while m > 0:
            count = min(k, m)  # 当前分组中物品的数量
            m -= count
            cost = count * v  # 当前分组的总租赁成本
            weight = count * w  # 当前分组的总载货量
            
            # 更新 DP 数组,从后向前遍历避免重复计算
            for j in range(V, cost - 1, -1):
                dp[j] = max(dp[j], dp[j - cost] + weight)
            
            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

输入:Q = 2, V = 10, ships = [[2, 3, 2], [3, 2, 10]]

  • 初始化:dp = [0] * 11

  • 对第 1 种货船(二进制分组:1 和 1):

    • 选择一艘:成本 3,载货量 2。
    • 两次遍历后,dp = [0, 0, 0, 2, 2, 2, 4, 4, 4, 4, 4]
  • 对第 2 种货船(二进制分组:1、2):

    • 第一轮:dp = [0, 0, 0, 2, 10, 10, 12, 12, 12, 20, 20]
    • 第二轮:dp = [0, 0, 0, 2, 10, 10, 12, 20, 20, 20, 32]

最终结果为 dp[10] = 32


复杂度分析

  1. 时间复杂度

    • 二进制分组:每种货船 m[i] 被分解为 O(log(m[i]))O(\log(m[i])) 件虚拟物品。
    • 动态规划:对每件虚拟物品遍历预算 VV,复杂度为 O(QVlog(m[i]))O(Q \cdot V \cdot \log(m[i]))
  2. 空间复杂度

    • 仅需维护一个大小为V + 1 的 DP 数组,复杂度为 O(V)。

总结

本题通过将多重背包问题转换为多个 0/1 背包子问题,并利用二进制分组优化枚举,实现了高效求解。动态规划中的状态转移逻辑清晰,适用于资源分配、成本优化等类似问题,是经典的算法模型之一。