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

53 阅读6分钟

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

问题描述

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

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

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

测试样例

样例1:

输入:n = 3, m = 2, s = 6, like = [1, 2, 3]

输出:5

样例2:

输入:n = 3, m = 1, s = 1, like = [1, 1, 1]

输出:6

样例3:

输入:n = 5, m = 3, s = 24, like = [1, 2, 3, 4, 5]

输出:1

样例4:

输入:n = 4, m = 0, s = 10, like = [1, 3, 3, 3]

输出:1

样例5:

输入:n = 6, m = 1, s = 35, like = [5, 5, 5, 5, 5, 5]

输出:0

解题思路

分析

题目要求计算有多少种不同的方案选择甜点,使得喜爱值之和正好为 S。每个甜点有三种处理方式:

  1. 不选该甜点。
  2. 选该甜点,不使用魔法棒,其喜爱值不变。
  3. 选该甜点,使用魔法棒,将喜爱值变为其阶乘。

需要注意的条件:

  • 每个甜点只能使用一次魔法棒。
  • 总共只有 M 根魔法棒。
  • 方案的不同体现在选择的甜点集合不同,或者使用魔法棒的甜点不同。

解法

考虑使用 DP 来解决这个问题。

状态定义

dp[i][j][k] 表示前 i 个甜点,使用了 j 根魔法棒,达到总和为 k 的方案数。

  • i:处理到第 i 个甜点。
  • j:已经使用的魔法棒数量,0 <= j <= M
  • k:当前喜爱值总和,0 <= k <= S

但三维 DP 的空间复杂度较高,考虑优化。

优化

由于每次处理一个甜点时,只与前一个状态有关,可以使用二维DP数组,表示使用 j 根魔法棒达到总和为 k 的方案数。

dp[j][k] 表示使用 j 根魔法棒,达到总和为 k 的方案数。

转移方程

对于每个甜点,有三种选择:

  1. 不选该甜点dp[j][k] 保持不变。

  2. 选该甜点,不使用魔法棒

    • 如果 k - like_i >= 0,则:
      dp[j][k] += dp_prev[j][k - like_i]
      
    • dp_prev 表示上一个状态的DP数组。
  3. 选该甜点,使用魔法棒

    • 如果 j > 0k - factorial(like_i) >= 0,则:
      dp[j][k] += dp_prev[j - 1][k - factorial(like_i)]
      
实现细节
  • 预计算每个甜点喜爱值的阶乘,以便在DP转移时使用。
  • 为了节省空间和时间,使用字典(defaultdict)来存储可能的 k 值。
  • 遍历甜点时,逆序遍历魔法棒的使用数量,防止重复计数。

算法步骤

  1. 预处理:计算每个甜点喜爱值的阶乘,存储在数组中。

  2. 初始化

    • dp[0][0] = 1,表示使用 0 根魔法棒,喜爱值总和为 0 的方案数为 1(什么都不选)。
  3. 遍历甜点

    对于每个甜点,执行以下操作:

    • 复制当前的 DP 状态,避免在同一轮更新时影响结果。

    • 遍历魔法棒数量

      • M0 逆序遍历 j

      • 遍历当前已有的喜爱值总和 k

        • 选该甜点,不使用魔法棒

          dp[j][k + like_i] += dp_prev[j][k]
          

          条件是 k + like_i <= S

        • 选该甜点,使用魔法棒

          dp[j + 1][k + factorial(like_i)] += dp_prev[j][k]
          

          条件是 j + 1 <= Mk + factorial(like_i) <= S

  4. 计算结果

    • 最终,遍历 0M 的魔法棒使用数量,将 dp[j][S] 累加,得到总的方案数。

注意

  • 避免重复计数:通过逆序遍历魔法棒数量,确保每个甜点的处理不会影响未处理的状态。
  • 空间优化:由于可能的总和范围较大,使用字典存储有效的 k 值,避免稀疏的二维数组浪费空间。

代码实现

def solution(n, m, s, like):
    from math import factorial
    from collections import defaultdict

    factorial_like = [factorial(l) for l in like]
    dp = [defaultdict(int) for _ in range(m + 1)]
    dp[0][0] = 1

    for i in range(n):
        current_like = like[i]
        transformed_like = factorial_like[i]

        for magic_wands_used in range(m, -1, -1):
            current_dp = dp[magic_wands_used].copy()
            for current_sum, count in current_dp.items():
                new_sum = current_sum + current_like
                if new_sum <= s:
                    dp[magic_wands_used][new_sum] += count

                if magic_wands_used + 1 <= m:
                    new_sum_transformed = current_sum + transformed_like
                    if new_sum_transformed <= s:
                        dp[magic_wands_used + 1][new_sum_transformed] += count

    total_ways = sum(dp[magic_wands_used][s] for magic_wands_used in range(m + 1))
    return total_ways

if __name__ == "__main__":
    print(solution(3, 2, 6, [1,2,3]) == 5 )
    print(solution(3, 1, 1, [1,1,1]) == 6 )
    print(solution(5, 3, 24, [1,2,3,4,5]) == 1)
    print(solution(4, 0, 10, [1,3,3,3]) == 1)
    print(solution(6, 1, 35, [5,5,5,5,5,5]) == 0)

测试用例说明

  • 样例1

    • 输入:n = 3, m = 2, s = 6, like = [1, 2, 3]
    • 输出:5
    • 解释:共有 5 种不同的方案,使得喜爱值总和为 6。
  • 样例2

    • 输入:n = 3, m = 1, s = 1, like = [1, 1, 1]
    • 输出:6
    • 解释:共有 6 种不同的方案,使得喜爱值总和为 1。
  • 样例3

    • 输入:n = 5, m = 3, s = 24, like = [1, 2, 3, 4, 5]
    • 输出:1
    • 解释:只有一种方案,选择甜点值为 4,使用魔法棒后喜爱值为 24(4! = 24)。
  • 样例4

    • 输入:n = 4, m = 0, s = 10, like = [1, 3, 3, 3]
    • 输出:1
    • 解释:没有魔法棒,只能选择所有 3 个喜爱值为 3 的甜点,喜爱值总和为 9(无法达到 10),但由于题目要求方案的喜爱值总和恰好为 S,所以只有一种方案,选择值为 1 和 3 的甜点。
  • 样例5

    • 输入:n = 6, m = 1, s = 35, like = [5, 5, 5, 5, 5, 5]
    • 输出:0
    • 解释:即使使用魔法棒将一个 5 变为 120(5!),其他 5 个甜点的喜爱值为 5,总和最小为 120 + 5 = 125,无法达到 35。

总结

本题考查了多重背包问题的变形,结合了魔法棒的使用,使得状态转移更加复杂。 通过动态规划和状态压缩,我们可以成功解决方案数的计算问题。