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

165 阅读4分钟

题目描述

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

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

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

解题思路

这个问题可以通过 动态规划(DP)来高效求解。每个甜点有两种选择:不使用魔法棒,或者使用魔法棒将其喜爱值变为阶乘。因此,对于每个甜点的选择,我们可以通过动态规划来逐步更新可能的方案数。

1. 状态表示

我们用一个动态规划的字典 f 来表示不同状态。字典的键 (a, b) 表示已经选择了 a 个甜点且它们的喜爱值总和为 b。值 v 表示达到该状态的方案数。

  • 初始状态:f[(0, 0)] = 1,表示没有选择任何甜点时,喜爱值和为 0 的方案数为 1。
2. 选择情况

每个甜点有两种可能的状态:

  • 不使用魔法棒,选择该甜点的原始喜爱值。
  • 使用魔法棒,选择该甜点的阶乘值。

我们遍历每个甜点,更新状态。

3. 状态转移

对于每一个状态 (a, b),我们可以:

  • 不使用魔法棒,更新为 (a, b + like[i])
  • 使用魔法棒,更新为 (a + 1, b + magic[like[i]]),其中 magic[i] 为该甜点的阶乘。
4. 最终答案

最后,我们统计所有满足条件的状态 (a, s),其中 a 的范围是 0ms 是目标值。

代码实现

from collections import defaultdict

# 计算阶乘的魔法数组
magic = [1] * 100

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

def solution(n, m, s, like):
    pre()
    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__":
    #  You can add more test cases here
    print(solution(3, 2, 6, [1, 2, 3]) == 5)  # 预期输出:5
    print(solution(3, 1, 1, [1, 1, 1]) == 6)  # 预期输出:6

代码解析

  1. 阶乘预处理

    • 使用 magic 数组来存储 1 到 99 的阶乘,提前计算并存储,提高效率。
  2. 动态规划表 f

    • 使用 defaultdict(int) 来保存状态,其中键 (a, b) 代表选择了 a 个甜点,喜爱值和为 b 的方案数。
  3. 状态更新

    • 对每个甜点,检查是否能够通过不使用魔法棒或者使用魔法棒更新当前的状态。
    • 使用 f.copy() 备份当前状态,避免在遍历时修改正在更新的状态。
  4. 返回结果

    • 最后统计所有满足条件的状态 (a, s),其中 a 可能从 0 到 m,累加得到最终的方案数。

测试与输出

print(solution(3, 2, 6, [1, 2, 3]) == 5)
print(solution(3, 1, 1, [1, 1, 1]) == 6)
  • 第一个测试solution(3, 2, 6, [1, 2, 3]) 返回 5,表示有 5 种方法可以使得所选甜点的喜爱值和为 6。
  • 第二个测试solution(3, 1, 1, [1, 1, 1]) 返回 6,表示有 6 种方法可以使得所选甜点的喜爱值和为 1。

时间与空间复杂度

  • 时间复杂度O(n * m * s),其中 n 是甜点数量,m 是魔法棒的数量,s 是目标总和。每次更新状态都需要遍历当前所有状态,因此复杂度为三者的乘积。
  • 空间复杂度O(n * s),由于我们需要记录所有可能的 (a, b) 状态。

个人思考与优化

  • 动态规划的优化

    • 当前方法通过复制 f 来备份状态,这样可以确保每次更新时不会相互干扰。但在实际中,完全可以通过逆序更新状态来减少空间开销,即直接从后向前更新 f 数组。
  • 阶乘增长问题

    • 随着 like[i] 值的增大,阶乘的值会迅速增长,这可能导致数值溢出或计算时间变长。可以通过限制 like[i] 的最大值来避免这一问题,或者直接检查是否可能超出目标 s
  • 性能提升

    • 如果 s 值非常大,可以考虑使用压缩存储的动态规划方法,例如只存储需要的状态,而不是维护整个二维数组。