1.题目
问题描述
小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
2.样例解析
样例1:
输入:n = 3, m = 2, s = 6, like = [1, 2, 3]
输出:
5
方案一:选中第三个甜点,使用魔法。次数1。
方案二:选中三个甜点,都不使用魔法。次数0。
方案三:选中三个甜点,且对第一个使用魔法。次数1.
方案四:选中三个甜点,且对第二个使用魔法。次数1.
方案五:选中三个甜点,且对第一个和第二个使用魔法。次数2次。总和是1! +2! +3=1+2+3=6。此时魔法次数是2,满足m=2的条件。
3.思路
-
定义状态和转换:
- 我们可以使用动态规划(DP)来解决这个问题。
dpmap[(stick_count, like_sum)]
:表示当前已选择了stick_count
个魔法棒,且当前的喜爱值总和为like_sum
时的选择方式数。- 初始时,
dpmap[(0, 0)] = 1
,即还没有选择任何甜点,魔法棒数目为 0,喜爱值和为 0,只有 1 种选择方式(空集)。
-
状态转移:
- 对于每个甜点,有两种选择:
- 不使用魔法棒:直接将当前甜点的喜爱值加入到
like_sum
中。 - 使用魔法棒:使用魔法棒后的喜爱值为阶乘值,将其加入到
like_sum
中,同时增加魔法棒的数量。
- 不使用魔法棒:直接将当前甜点的喜爱值加入到
通过遍历所有状态,并依次更新
dpmap
,考虑每个甜点的两种选择方式。 - 对于每个甜点,有两种选择:
-
更新
dpmap
:- 每次选择甜点时,先将当前状态
dpmap
进行复制,避免在遍历时修改原始数据。 - 对于每个状态
(stick_count, like_sum)
,尝试两种选择:- 加入当前甜点的普通喜爱值,如果不超过
s
,更新状态。 - 加入当前甜点的阶乘喜爱值,并且使用魔法棒数不超过
m
,更新状态。
- 加入当前甜点的普通喜爱值,如果不超过
- 每次选择甜点时,先将当前状态
-
计算结果:
- 最终结果是所有使用不超过
m
个魔法棒,且喜爱值总和为s
的所有选择方式数。通过遍历dpmap
,累计所有符合条件的选择方式。
- 最终结果是所有使用不超过
4.代码
from collections import defaultdict
# 初始化一个 magic 数组,用来存储每个整数的阶乘,最大支持到 99!
magic = [1] * 100
def solution(n, m, s, like):
# Please write your code here
# 计算每个整数的阶乘并存储到 magic 数组中
for i in range(1, 100):
magic[i] = magic[i - 1] * i
# dpmap 是一个默认字典,用于记录每种状态的方案数
# 键是一个元组 (stick_count, like_sum),分别表示使用了多少个魔法棒和目前的喜爱值之和
# 值是该状态下的方案数
dpmap = defaultdict(int) # 默认值类型为 int
dpmap[(0, 0)] = 1 # 没有选择任何甜点,没有使用魔法棒,喜爱值和为 0 的状态有 1 种方式(即空集)
# 遍历每一个甜点
for i in range(n):
# copy 是为了防止当前状态被污染,保证每次遍历都不会受到修改的影响
current_dpmap = dpmap.copy()
# 遍历所有当前已存储的状态
for (stick_count, like_sum), count in current_dpmap.items():
# 情况 1: 当前甜点不使用魔法棒
if like_sum + like[i - 1] <= s:
# 如果加入当前甜点不超过目标值 S,则更新状态
dpmap[(stick_count, like_sum + like[i - 1])] += count
# 情况 2: 当前甜点使用魔法棒
if stick_count + 1 <= m and like_sum + magic[like[i - 1]] <= s:
# 如果使用魔法棒后的喜爱值和不超过 S 且魔法棒的数量没有超过 m,则更新状态
dpmap[(stick_count + 1, like_sum + magic[like[i - 1]])] += count
# 最终的结果是所有使用不超过 m 个魔法棒,且喜爱值和为 S 的方案数之和
result = 0
for i in range(m + 1):
result += dpmap[(i, s)]
return result
if __name__ == "__main__":
# You can add more test cases here
print(solution(3, 2, 6, [1,2,3]) == 5 )
print(solution(3, 1, 1, [1,1,1]) == 6 )
5.参考资料
【题解】魔法甜点之和:小包的新挑战 | 豆包MarsCode AI刷题本文通过DP结合空间离散化(使用哈希表存储状态)的 - 掘金