青训营X豆包MarsCode技术训练营第六课|豆包MarsCode刷题

56 阅读10分钟

今天刷的也是一道难度特别难的题目:小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

下面我将详细分析这道题目,帮助你理解问题的背景、建模思路以及如何利用动态规划来解决它。

题目分析

题目描述了一个背包问题变种,我们需要在给定的预算下选择若干艘货船以最大化总载货量。问题的关键点是:

  1. 货船的选择具有数量限制:每种货船有数量限制,这意味着我们不能无限制地选择某一种货船。
  2. 每艘货船的租赁费用和载货量已知:每种类型的货船都有固定的租赁费用和载货量。
  3. 预算限制:我们有一个总的预算,不能超过这个预算来租用货船。

因此,问题的目标是,在预算范围内,通过选择合适数量的货船类型,使得载货量最大化。

问题建模

这道题可以视作一个 带有数量限制的背包问题(也称为“多重背包问题”)。在标准的背包问题中,背包只能选择物品一次,而在本题中,背包物品(货船)是有限个的,每种货船可以选择的数量是有限制的。我们需要通过动态规划来求解最优解。

动态规划解法

  1. 定义状态

    • 使用一个一维数组 dp[j] 来表示预算为 j 时,能够达到的最大载货量。
    • 其中,dp[j] 的意义是,当预算为 j 时,最大载货量是多少。
  2. 状态转移

    • 对于每种货船 i,其价格为 v[i],载货量为 w[i],并且每种货船的最大数量为 m[i],我们需要考虑在预算 j 下选择该货船的不同数量。
    • 我们从预算 V 开始倒推,确保每种货船在同一轮计算中不会被重复使用。
  3. 更新 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] 艘货船。
  4. 边界条件

    • 初始化 dp[0] = 0,表示当预算为 0 时,最大载货量是 0。
    • 其他的 dp[j] 初始化为 0,因为在没有租用货船的情况下,载货量为 0。
  5. 最终解

    • 最终答案就是 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

代码分析

  1. 初始化

    • dp = [0] * (V + 1) 初始化一个大小为 V + 1 的数组,用于存储每个预算下的最大载货量。
  2. 外层循环:遍历每种货船类型,使用参数 m[i], v[i], w[i],其中:

    • m[i] 表示最多可以选择的该类型货船数量。
    • v[i] 是该货船的租赁价格。
    • w[i] 是该货船的载货量。
  3. 内层循环

    • 第一层循环 for j in range(V, v - 1, -1):从大到小遍历预算 j,确保每个预算只计算一次,避免重复选择相同类型的货船。
    • 第二层循环 for k in range(1, m + 1):对于每种货船,考虑从 1 到最多 m[i] 艘货船的选择,检查选择 k 艘货船是否可行(即当前预算 j 是否能够支付 k 艘货船的租赁费用)。
  4. 转移方程

    • 如果当前预算 j 足够支付 k 艘货船的费用,则更新 dp[j],最大化当前预算下的载货量。
  5. 最终输出

    • 结果存储在 dp[V] 中,即在预算为 V 时,能够达到的最大载货量。

复杂度分析

  • 时间复杂度
    • 外层遍历所有货船类型(Q次),每次遍历需要更新一个大小为 V 的数组。
    • 对于每种货船,我们还需要考虑最多 m[i] 种选择方式。所以,整体时间复杂度是 O(Q * V * m),其中 Q 是货船种类数,V 是预算,m 是货船的最大数量。
  • 空间复杂度
    • 我们只需要一个大小为 V + 1dp 数组,因此空间复杂度是 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 时,背包中能获得的最大价值。转移方程如下:

  1. 状态转移: 对于每种物品 i,其重量为 w_i,价值为 v_i,最多可选 c_i 个。我们需要更新 dp 数组:

    • 可以考虑从 1 个到 c_i 个物品的选择情况。
    • 从最大容量 W 开始倒推更新,以避免重复使用相同物品。
  2. 转移方程: [ dp[j] = \max(dp[j], dp[j - k \times w_i] + k \times v_i) ] 其中,k 是当前选择的物品的数量,k1c_i

举例说明

假设有3种物品和一个容量为5的背包,物品的属性如下:

物品重量 (w_i)价值 (v_i)最大数量限制 (c_i)
1232
2341
3451

背包容量为 5,我们需要选择物品,使得总价值最大。每种物品的数量限制如下:

  • 物品1最多选择2个,
  • 物品2最多选择1个,
  • 物品3最多选择1个。

我们使用动态规划来求解这个问题,定义 dp[j] 为容量为 j 时背包的最大价值。

  1. 初始化 dp[0] = 0(没有任何物品,价值为0)。
  2. 遍历每种物品,更新背包的最大价值。
    • 对于物品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+1dp 数组,空间复杂度为 O(W)

总结

多重背包问题是经典的背包问题变种,在实际应用中,许多实际问题都可以归结为多重背包问题。通过动态规划的方法,我们能够在有限时间内计算出最优解。