青训营X豆包MarsCode 技术训练营笔记3 | 豆包MarsCode AI 刷题

87 阅读4分钟

魔法甜点之和:小包的新挑战

问题描述

小R不再追求甜点中最高的喜爱值,今天他想要的是甜点喜爱值之和正好匹配他的预期值 S。为了达到这个目标,他可以使用魔法棒来改变甜点的喜爱值,使其变为原来喜爱值的阶乘。每个甜点只能使用一次魔法棒,也可以完全不用。

下午茶小哥今天带来了 N 个甜点,每个甜点都有一个固定的喜爱值。小R有 M 个魔法棒,他可以选择任意甜点使用,但每个甜点只能使用一次魔法棒。他的目标是通过选择一些甜点,可能使用魔法棒,使得这些甜点的喜爱值之和恰好为 S。

请计算小R有多少种不同的方案满足他的要求。如果两种方案中,选择的甜点不同,或者使用魔法棒的甜点不同,则视为不同的方案。

解题思路

这个问题可以转化为一个典型的背包问题,并结合动态规划进行求解。问题的关键点在于如何使用魔法棒将甜点的喜爱值转换为其阶乘,并在此过程中确保结果满足预期和 S

可以通过动态规划,定义 dp[i][j] 来表示使用前 i 个甜点,选择了 j 个魔法棒,当前得到的喜爱值之和。状态转移的核心思路是:对于每个甜点,我们有两种选择:

  1. 不使用魔法棒,直接将该甜点的喜爱值加入到当前的和中。
  2. 使用魔法棒,将该甜点的喜爱值转化为它的阶乘,并将阶乘值加入到当前的和中。

输入n:表示甜点的数量。

m:表示魔法棒的数量。

s:目标和,表示小R希望选择甜点后它们的喜爱值和为 s

like:长度为 n 的数组,表示每个甜点的喜爱值。

代码如下

from collections import defaultdict

# 计算阶乘并保存
magic = [1] * 100

def pre():
    for i in range(1, 100):
        magic[i] = magic[i - 1] * i

pre()  # 初始化magic数组

def solution(n, m, s, like):
    f = defaultdict(int)  # 使用defaultdict来初始化map
    f[(0, 0)] = 1  # 初始状态

    # 动态规划求解
    for i in range(1, n + 1):
        g = f.copy()  # 备份当前的状态
        for (a, b), v in g.items():
            # 不使用魔法棒,直接加上当前甜点的喜爱值
            if b + like[i - 1] <= s:  # 加入"喜欢"的数量
                f[(a, b + like[i - 1])] += v
            # 使用魔法棒,加入当前甜点的阶乘值
            if a + 1 <= m and b + magic[like[i - 1]] <= s:  # 加入"喜欢的阶乘"
                f[(a + 1, b + magic[like[i - 1]])] += v

    # 计算符合条件的方案数
    sum_ = 0
    for i in range(m + 1):
        sum_ += f[(i, s)]  # 累加满足条件的结果

    return sum_

if __name__ == "__main__":
    print(solution(3, 2, 6, [1, 2, 3]) == 5)
    print(solution(3, 1, 1, [1, 1, 1]) == 6)

代码解析

首先,预处理阶乘值。由于每个甜点的喜爱值最大为 99,我们可以通过计算 1!2!, ..., 99! 来提前存储阶乘值,避免在每次计算时重复计算。然后使用一个 f 来存储动态规划的状态,其中 f[(a, b)] 表示使用了 a 个魔法棒后,当前的喜爱值和为 b 的方案数。初始化状态是 f[(0, 0)] = 1,即表示没有选择任何甜点且没有使用魔法棒时,当前和为 0 的方案数为 1。然后,对于每个甜点更新状态,最后,我对所有符合条件(即使用 m 个魔法棒且和为 s)的方案数,求和累加。

复杂度分析

  • 时间复杂度:O(n * m * s),其中 n 是甜点的数量,m 是魔法棒的数量,s 是目标和。我们需要遍历每个甜点并更新每个可能的状态。
  • 空间复杂度:O(n * m * s),我们使用了一个大小为 m * s 的字典来存储动态规划状态。

总结

在算法学习中遇到这类问题时,首先要明确状态定义,然后仔细分析每种选择的影响,设计合适的状态转移方程,最后通过动态规划来高效地计算出答案。动态规划解决的问题通常都具有“最优子结构”和“重叠子问题”的特点。通过不断练习不同类型的动态规划题目,可以逐渐培养解决复杂问题的思维方式。 同时,解决此类问题时,注意以下几点:

  1. 清晰定义状态:确定问题的状态,并明确每个状态的含义。
  2. 仔细分析状态转移:思考如何从一个状态转移到另一个状态,尤其是在做选择的时候。
  3. 记得边界条件:初始化状态和边界条件是确保动态规划正确性的重要一步。
  4. 空间优化:有时候我们不需要保留所有历史状态,可以考虑空间优化,减少内存使用。