MarsCode AI 刷题思路解析:66. 魔法甜点之和:小包的新挑战 | 豆包MarsCode AI刷题

42 阅读4分钟

问题描述

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

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

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

数学重述

小 R 面对 N 个甜点,每个甜点都有固定的喜爱值( like 值 )。为了满足他独特的需求,他想要选取一些甜点使得它们的喜爱值之和正好等于 S。为了实现这一目标,小 R 有 M 根魔法棒,每根魔法棒可以将甜点的喜爱值变为该值的阶乘(例如,2 的阶乘为 2!=2×1=22! = 2 \times 1 = 2)。

每个甜点可以:

  1. 原样选择。
  2. 使用魔法棒变成喜爱值的阶乘。

目标是计算满足条件的所有可能方案的总数。如果两种方案中选择的甜点或使用魔法棒的甜点不同,则视为不同方案。

问题分析

这是一个典型的动态规划问题,但相比传统背包问题增加了以下复杂性:

  1. 状态扩展:不仅可以选择甜点原始值,还可以选择其阶乘值。

  2. 双重限制:方案受两方面的限制 —— 总和 SS 和魔法棒的使用次数 MM

样例分析

输入:N=3,M=2,S=6,like=[1,2,3]N = 3, M = 2, S = 6, \text{like} = [1, 2, 3]

输出:55

样例分析: 有 5 种方案让喜爱值之和为 66

  • 不使用魔法棒:[1, 2, 3]

  • 使用一次魔法棒:阶乘变换 3!=63! = 6,结果:[1, 6]

解题思路

使用动态规划( Dynamic Programming, DP )来解决,定义状态和转移方式如下:

  1. 状态定义
    • f[a][b]f[a][b]:表示使用了 aa 根魔法棒,且选取甜点后总和为 bb 的方案数。
  2. 状态转移
    • 如果当前甜点使用了魔法棒,其阶乘值加入总和,更新方案数。
    • 如果当前甜点不使用魔法棒,其原始值加入总和,更新方案数。

算法步骤

  1. 预处理:计算 0 到最大值的阶乘,存储在数组 magic 中,便于快速查询。
  2. 初始化状态:初始时 f[0][0]=1f[0][0] = 1(没有选甜点,没有使用魔法棒,总和为 0 的方案数为 1)。
  3. 逐步处理每个甜点
    • 对每种可能的状态 (a,b)(a, b),尝试两种选择:
      • 不使用魔法棒:总和增加原始喜爱值。
      • 使用魔法棒:总和增加阶乘值,并增加魔法棒使用数。
  4. 统计结果:所有符合条件的 f[a][S]f[a][S] 累加,得到最终方案数。

代码分解

1. 函数定义与输入解析

定义了解决问题的主函数,参数包括字符串的长度 ( n )、可用操作次数 ( k )、以及由 '0' 和 '1' 组成的字符串 ( s )。通过将字符串转换为列表形式,便于进行后续的字符交换操作。

def solution(n: int, k: int, s: str) -> str:
    s = list(s)  # 将字符串转换为列表以便操作

2. 遍历字符串中的每个字符

逐个检查字符串的每个位置。如果当前字符为 '0',无需操作,直接跳过。若剩余的操作次数 ( k ) 为 0,说明已经无法再进行交换,直接退出循环。

    for i in range(n):
        if k <= 0:
            break  # 如果没有剩余的操作次数,退出
        
        if s[i] == '0':
            continue  # 如果当前字符是 '0',则无需移动

3. 寻找并替换右侧的最近 '0'

从当前字符右侧寻找最近的 '0',并计算将其移动到当前位置所需的交换次数。如果所需交换次数 ( \text{needed_swaps} ) 不超过剩余的操作次数 ( k ),执行交换操作并减少 ( k )。每找到一个可用的 '0' 就退出内层循环,避免重复处理。

        # 需要将字符 '0' 移到当前位置 i 之前
        for j in range(i + 1, n):
            if s[j] == '0':
                # 计算需要的交换次数
                needed_swaps = j - i
                if needed_swaps <= k:
                    # 执行交换
                    for swap in range(j, i, -1):
                        s[swap], s[swap - 1] = s[swap - 1], s[swap]
                    k -= needed_swaps  # 减少可用的交换次数
                break  # 找到一个 '0' 之后就可以退出内层循环

4. 返回最终结果

将修改后的字符列表重新转换为字符串,并返回结果。

    return ''.join(s)

5. 测试代码

通过多组测试用例验证代码的正确性,确保输出符合期望结果。

if __name__ == '__main__':
    print(solution(5, 2, "01010") == '00101')  # True
    print(solution(7, 3, "1101001") == '0110101')  # True
    print(solution(4, 1, "1001") == '0101')  # True

运行结果

image.png