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

69 阅读5分钟

题目解析

问题背景

小R有N个甜点,每个甜点都有一个固定的喜爱值。小R手上有M个魔法棒,他的目标是通过选择一些甜点,可能使用魔法棒使得这些甜点的喜爱值之和恰好为S。

问题解读

每个甜点的喜爱值可以通过两种方式来影响:

  1. 不使用魔法棒:直接取该甜点的喜爱值。
  2. 使用魔法棒:该甜点的喜爱值变为它的阶乘。

小R的目标是选择一些甜点,使用魔法棒或不使用魔法棒,使得这些甜点的喜爱值之和恰好为S,并且不超过M次使用魔法棒。

问题分析

本题可以分为以下几个步骤:

  1. 选择甜点:每个甜点可以选择或不选择,选择后可以决定是否使用魔法棒。
  2. 阶乘计算:每个甜点的阶乘值是有限制的,如果阶乘值超过S,就无法使用魔法棒。
  3. 分治法:由于有N个甜点,我们可以将问题拆成两部分,分别处理前半部分和后半部分。这样有助于减少问题的规模,使得每个子问题的规模较小,避免了暴力搜索带来的计算复杂度。

思路与解法

1. 分治法

由于问题中存在N个甜点和M个魔法棒,我们可以将问题分为两部分来解决。具体做法如下:

  • 将甜点数组分为两部分:likes_firstlikes_second
  • 分别计算每部分的所有可能的组合,包括选择的甜点和使用魔法棒的次数。
  • 使用字典(sum_counts_firstsum_counts_second)存储每部分所有可能的“喜爱值总和”和“使用魔法棒的次数”。

2. 动态规划+递归

  • 递归地遍历每一部分甜点,计算其所有可能的组合,包括不使用魔法棒、使用魔法棒、以及阶乘计算。
  • 使用一个缓存机制来避免重复计算阶乘,确保计算效率。

3. 合并结果

  • 计算完两部分后,我们将它们的结果进行合并,检查两部分组合的喜爱值之和是否等于S,并且魔法棒的使用次数总和不超过M。

代码详解

def solution(n, m, s, like):
    from collections import defaultdict
    import sys
    sys.setrecursionlimit(1 << 25)  # 增大递归深度限制

    # 预计算阶乘,避免重复计算
    factorial_cache = {}

    def factorial(x):
        if x in factorial_cache:
            return factorial_cache[x]
        result = 1
        for i in range(2, x + 1):
            result *= i
            if result > s:
                result = s + 1  # 超过 s 的阶乘值设为 s+1,用于剪枝
                break
        factorial_cache[x] = result
        return result

    # 将甜点分为两部分
    mid = n // 2
    likes_first = like[:mid]
    likes_second = like[mid:]

    # 处理第一部分
    sum_counts_first = defaultdict(lambda: defaultdict(int))

    def dfs_first(i, total_like, wands_used):
        if i == len(likes_first):
            sum_counts_first[total_like][wands_used] += 1
            return
        # 不选择当前甜点
        dfs_first(i + 1, total_like, wands_used)
        # 选择当前甜点,不使用魔法棒
        dfs_first(i + 1, total_like + likes_first[i], wands_used)
        # 选择当前甜点,使用魔法棒(如果阶乘值不超过 s)
        fact = factorial(likes_first[i])
        if fact <= s:
            dfs_first(i + 1, total_like + fact, wands_used + 1)

    dfs_first(0, 0, 0)

    # 处理第二部分
    sum_counts_second = defaultdict(lambda: defaultdict(int))

    def dfs_second(i, total_like, wands_used):
        if i == len(likes_second):
            sum_counts_second[total_like][wands_used] += 1
            return
        # 不选择当前甜点
        dfs_second(i + 1, total_like, wands_used)
        # 选择当前甜点,不使用魔法棒
        dfs_second(i + 1, total_like + likes_second[i], wands_used)
        # 选择当前甜点,使用魔法棒(如果阶乘值不超过 s)
        fact = factorial(likes_second[i])
        if fact <= s:
            dfs_second(i + 1, total_like + fact, wands_used + 1)

    dfs_second(0, 0, 0)

    # 合并两部分的结果
    total_ways = 0
    for sum_first in sum_counts_first:
        sum_second_needed = s - sum_first
        if sum_second_needed in sum_counts_second:
            counts_first = sum_counts_first[sum_first]
            counts_second = sum_counts_second[sum_second_needed]
            for wands_first in counts_first:
                for wands_second in counts_second:
                    if wands_first + wands_second <= m:
                        total_ways += counts_first[wands_first] * counts_second[wands_second]

    return total_ways

关键点分析

  1. 阶乘计算

    • 为了避免重复计算阶乘,我们使用了一个缓存(factorial_cache)来存储已经计算过的阶乘结果。
    • 如果阶乘值超出S,我们立即停止计算并返回S+1,这样可以通过剪枝来减少计算量。
  2. 分治法

    • 将甜点分成两部分进行处理,每一部分递归地计算所有可能的组合。
    • 通过sum_counts_firstsum_counts_second分别记录每一部分的结果,从而避免了暴力枚举。
  3. 合并结果

    • 在最终合并结果时,我们通过遍历两部分的可能结果,检查是否满足喜爱值之和为S且魔法棒使用次数不超过M。

知识总结与个人思考

知识点总结

  1. 递归与分治法:通过将大问题拆解为两个较小的子问题,可以显著降低问题的复杂度,避免暴力枚举。
  2. 动态规划与缓存:通过缓存计算结果(例如阶乘),避免重复计算,从而提高了程序效率。
  3. 剪枝优化:通过对阶乘值进行剪枝,可以避免不必要的计算,进一步优化性能。

学习建议

  1. 递归思维:遇到这类问题时,应该尝试将问题拆分成更小的子问题,分治法能够帮助我们有效解决。
  2. 缓存与优化:在遇到重复计算时,考虑使用缓存或记忆化技术来避免重复计算,从而提高效率。
  3. 阶乘计算与剪枝:在处理阶乘问题时,需要注意阶乘增长速度较快,因此通过剪枝优化,可以避免过大的数字影响计算结果。

总的来说,本题通过分治法和递归来优化问题的求解过程,并使用缓存和剪枝技术大幅提高了效率。对于入门同学,理解递归的拆解和优化技术是非常有帮助的。