AI刷题:魔法甜点之和:小包的新挑战 | 豆包MarsCode AI刷题

54 阅读5分钟

题目解析:魔法甜点之和:小包的新挑战

一、题目背景

这次要解析的是一道较为复杂的组合求和问题。题目描述小R希望通过选择一些甜点,最终使得这些甜点的喜爱值之和等于一个预期的目标值 。为了达到这一目标,小R拥有 根魔法棒,可以将甜点的喜爱值转化为其阶乘值。甜点的喜爱值一旦转化为阶乘后,数值会急剧增加,增加了找到合适组合的难度。

题目要求找到满足条件的所有可能组合数,并要求每种组合可以选择使用或不使用魔法棒。这类问题的解题思路与多重背包问题类似,属于典型的动态规划问题。本文将从思路设计到代码实现逐步展开,帮助理解问题背后的逻辑。

二、思路解析

在解决这个问题时,我们首先把它看作是多重背包问题。为了理解这道题的求解过程,我们可以从以下几个方面入手:

  1. 状态定义 我们使用一个二维动态规划数组 dp[j][k] 来表示状态,其中:

    • 表示当前甜点喜爱值的总和。
    • 表示使用了多少根魔法棒。
    • dp[j][k] 代表使用前若干个甜点,最多使用 根魔法棒时,得到甜点喜爱值之和为 的方案数。
  2. 状态转移方程 对于每个甜点的喜爱值 ,我们有两种选择:

    • 不使用魔法棒:直接将甜点的原始喜爱值加入到当前状态。即:

      ​​dp[j][k] += dp[j-a][k]

    • 使用魔法棒:将甜点喜爱值转换为其阶乘值 ,然后更新状态。即:

      ​​dp[j][k] += dp[j-f(a)][k-1],前提是 且 。

  3. 初始状态

    • 初始化 dp[0][0] = 1,表示没有使用任何甜点且魔法棒数量为 0 时,喜爱值为 0 的方案数为 1。
    • 其他状态初始为 0。
  4. 遍历顺序 我们对每个甜点进行遍历,然后逆序更新 和 ,以防止在同一轮次中重复计算同一个甜点。对于每个甜点,先遍历目标值 从高到低,再遍历魔法棒数量,从最多使用的数量递减,这样可以保证每个甜点只在该轮次中计算一次,避免重复。

三、代码实现

以下是该题的代码实现:

from math import factorial

def solution(n, m, s, like):
    # Please write your code here
    factorial_values = [factorial(x) if factorial(x) <= s else float('inf') for x in like]
    
    dp = [[0] * (m + 1) for _ in range(s + 1)]
    dp[0][0] = 1  
    
    for a, f in zip(like, factorial_values):
        for j in range(s, -1, -1):
            for k in range(m, -1, -1):
                if j >= a:
                    dp[j][k] += dp[j - a][k]  
                if k >= 1 and j >= f and f != float('inf'):
                    dp[j][k] += dp[j - f][k - 1]  
    
    return sum(dp[s])

四、代码详解

  1. 阶乘预计算

    • 由于阶乘值增长非常快,因此我们在开始动态规划之前,对每个甜点的喜爱值计算其阶乘值。如果阶乘值超过了目标值 ,则将其标记为无穷大(float('inf')),这样可以有效地避免不必要的计算。
  2. 动态规划数组初始化

    • 我们使用二维数组 dp 来存储状态信息,dp[j][k] 表示当前甜点喜爱值和为 ,且使用了 根魔法棒的所有方案数。初始状态下,dp[0][0] 设为 1,表示不选择任何甜点且不使用魔法棒的初始状态。
  3. 状态转移逻辑

    • 在每次遍历甜点时,我们对目标值和魔法棒数量进行逆序遍历,从而保证每个甜点在每轮次只被使用一次。
    • 不使用魔法棒时,直接累加原始喜爱值对应的方案数。
    • 使用魔法棒时,累加阶乘值对应的方案数,前提是阶乘值有效且魔法棒数量足够。
  4. 结果输出

    • 最终我们需要的结果是使用最多 根魔法棒,使得甜点喜爱值之和为 的所有方案数之和,即 sum(dp[s])

五、个人思考与总结

在解决这个问题的过程中,使用动态规划来应对复杂的多重约束问题是一种有效的策略。特别是在处理类似背包问题的情况下,合理设计状态空间和状态转移方程可以大大减少问题的复杂性。

  1. 状态压缩与剪枝的重要性

    • 在本题中,阶乘值的计算会迅速增大,使得状态空间可能变得不可控。因此,提前对阶乘值进行剪枝过滤,可以有效降低后续计算的复杂度。这种策略在面对大量数据时尤为重要。
  2. 逆序遍历防止重复计算

    • 逆序遍历是动态规划中常用的技巧,尤其是在处理多重背包或类似问题时。这样可以确保每个元素在当前轮次中只被使用一次,避免状态被重复更新。
  3. 动态规划的应用范围

    • 通过本题的练习,我深刻体会到动态规划在解决组合问题上的强大之处。它不仅适用于简单的最优问题,在处理有多重约束条件的复杂场景时,动态规划同样能够提供高效的解法。