刷题笔记:水果店果篮最小成本问题 | 豆包MarsCode AI刷题

93 阅读5分钟

问题描述

小C开了一家水果店,接到一个大订单,需要将 n 个编号为 1 到 n 的水果打包成多个果篮。每个果篮的最大容量为 m 个水果,并且水果的编号必须是连续的。每个果篮的成本是根据水果的体积来计算的,成本公式如下:

image.png

其中:

  • k 是果篮中水果的数量。
  • u 是果篮中水果的最大体积。
  • v 是果篮中水果的最小体积。
  • s 是一个常数。

目标是将 n 个水果分成若干个果篮,使得总成本最小。

问题剖析

  1. 果篮的最大容量:每个果篮最多只能装 m 个水果,且水果编号必须是连续的。
  2. 成本计算公式:对于每个果篮,我们需要计算其最大体积、最小体积,然后根据公式计算该果篮的成本。
  3. 最小化总成本:我们需要将水果分组成多个果篮,且每个组的水果编号是连续的,目标是使得分组的总成本最小。

解题思路

  1. 动态规划: 我们可以利用动态规划来解决这个问题。设定 dp[i] 表示将前 i 个水果分成若干个果篮的最小成本。

  2. 状态转移: 对于每个水果 i,我们可以考虑从 ji 这一段水果作为一个果篮,其中 j 代表上一个果篮的结束位置。通过计算该段水果的最大体积 u 和最小体积 v,可以得到这一段果篮的成本。

  3. 状态转移公式

    • 对于每一个 i,从 i - mi-1 的每一个可能的 j 都可以形成一个新的果篮。我们需要根据当前的 ji 来计算果篮的成本并更新 dp[i]
    • 使用最大体积和最小体积来计算当前果篮的成本,更新 dp[i]
  4. 最终目标: 最终,dp[n] 即为将 n 个水果分成若干个果篮的最小成本。

代码实现

python
复制代码
def solution(n: int, m: int, s: int, a: list) -> int:
    # dp[i]表示前i个水果打包成若干个果篮的最小成本
    dp = [float('inf')] * (n + 1)
    dp[0] = 0  # 没有水果时,成本为0
    
    # 遍历每个可能的水果数目
    for i in range(1, n + 1):
        max_val = a[i - 1]
        min_val = a[i - 1]
        # 尝试将i个水果放到不同的果篮
        for j in range(i - 1, max(i - m - 1, -1), -1):
            # 更新当前果篮的最大最小体积
            max_val = max(max_val, a[j])
            min_val = min(min_val, a[j])
            # 计算当前果篮的成本
            cost = (i - j) * ((max_val + min_val) // 2) + s
            # 更新dp[i]的值
            dp[i] = min(dp[i], dp[j] + cost)
    
    return dp[n]

# 测试用例
if __name__ == '__main__':
    print(solution(6, 4, 3, [1, 4, 5, 1, 4, 1]) == 21)
    print(solution(5, 3, 2, [2, 3, 1, 4, 6]) == 17)
    print(solution(7, 4, 5, [3, 6, 2, 7, 1, 4, 5]) == 35)

代码解析

  1. 初始化

    • dp 数组用于记录到每个水果位置 i 的最小成本。初始化时,dp[0] = 0,表示没有水果时没有成本。
    • 其他 dp[i] 初始为正无穷,表示还没有计算到达该位置的最小成本。
  2. 动态规划的状态转移

    • 对于每个位置 i,我们尝试选择一个合适的起始位置 j 来划定一个新的果篮(a[j...i])。每次更新 dp[i],计算从 ji 的果篮成本,并更新 dp[i] 为最小值。
  3. 成本计算

    • 对每一个划定的果篮,我们计算它的最大值 max_val 和最小值 min_val,然后根据给定的公式计算成本。
  4. 返回结果

    • 最终 dp[n] 给出了最优的打包方案对应的最小成本。

时间复杂度分析

  • 外层循环遍历所有 n 个水果,时间复杂度为 O(n)
  • 内层循环会遍历至 m 个水果,计算每个果篮的成本,所以最坏情况下每次内层循环的复杂度为 O(m)

因此,总的时间复杂度为 O(n * m)

刷题思路总结

  1. 动态规划的应用:通过动态规划思想,我们能够分解问题,将其转化为求解子问题的形式。每个状态 dp[i] 表示前 i 个水果的最小打包成本,而每个子问题 dp[i] 依赖于前面 m 个水果的选择情况。
  2. 状态转移的细节:内层循环的设计是从当前水果位置 i 向前探索所有可能的划分位置 j。这种方法可以确保每个可能的果篮分组都能被计算到。
  3. 复杂度控制:虽然内层循环可能看起来有点多余,但通过限制内层循环的最大迭代次数为 m,我们能够控制算法的时间复杂度。
  4. 数学公式的理解:题目中的成本公式需要理解如何根据最大值、最小值和水果数量来计算成本,这一部分逻辑需要清晰地在代码中实现。

通过这道题,可以进一步加强对动态规划的理解,特别是在涉及最小化或最大化问题时的应用。同时,注意到每个果篮的最大容量和连续性要求,解决了一个实际的分组问题。