原问题描述
众所周知,小包是一名非常喜欢吃甜点的小朋友,他在工作时特别爱吃下午茶里的甜食。 这天,下午茶小哥像往常一样送来了今天的 N 个甜点。小包对每个甜点有自己的喜爱值。但是今天的他不再贪心,并不想要喜爱值越高越好。他今天吃的甜点的喜爱值的和,一定要等于他的预期值 S。 但是他的预期值很高,小哥的甜点似乎不一定满足得了他,所以他准备了 M 个魔法棒,每个魔法棒可以对一个他要吃的甜点使用 1 次,使用后这个甜点的喜爱值会变成原来的喜爱值的阶乘。无法对一个甜点使用多次魔法棒,也不需要使用完所有魔法棒,也无法对不吃的甜点使用魔法棒。 小包很好奇,他有多少种方案,可以吃到喜爱值刚好为他的预期值的甜点。如果 2 种方案,食用了不同的甜点,或者对不同的甜点使用了魔法棒,都算作不同的方案。
这道题的解题思路主要围绕背包问题和组合求和的策略进行,结合了DFS(深度优先搜索)和动态规划的理念。首先,我们需要明确问题中的几个核心要素:我们有N个甜点,每个甜点的喜爱值对于小包来说是一个正整数;小包希望选择一些甜点,使得这些甜点的喜爱值和等于给定的期望值S。与此同时,小包可以对每个甜点使用一次魔法棒,使得这些甜点的喜爱值转变为它们的阶乘,这为选择甜点和计算总和的方式增添了复杂性。
以下是我的代码实现:
# 计算阶乘的魔法数组
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 )
print(solution(3, 1, 1, [1,1,1]) == 6 )
在解决这类问题时,我们可以将其视为一个组合总和问题的变种。具体来说,我们可以使用DFS来探索每个甜点是否应该被选择,以及是否需要对其使用魔法棒。对每个甜点,只有三种选择:不选、不使用魔法棒、使用魔法棒。通过递归调用,我们对每个选择分支都进行深度搜索,直至遍历完所有甜点。我们需要维护一个组合的和,并在其等于期望值S时记录成功的组合方案。
在实现过程中,关键在于维护状态。这包括当前的和、当前的甜点索引以及剩余的魔法棒数。每次递归调用中,我们从当前的索引开始,考察所有可能的组合,注意递归中的状态对应必须合理回溯,确保不会对某个甜点使用魔法棒超过一次。
此外,题目也可与动态规划相结合,尤其是当S较大,且N和M也不是非常小的时候。DP方法能够帮助我们优化状态的存储。例如,一维DP数组可以用来表示达到某一喜爱值所需的组合方式。
这道题的解题思路诠释了在面对组合求和类问题时,如何利用DFS进行遍历以及如何加入额外的状态(本题为魔法棒的使用),使得解法可以覆盖所有可能的方案。同时,它也提醒我们动态规划可以在状态复杂度较高时起到简化和优化的作用。对以后做题的启示在于:遇到组合问题时,首先准确识别状态、选择合适的遍历方式(DFS或动态规划),并确保对状态进行合理的回溯和维护,可以有效地求解类似复杂的组合问题。最终,通过叠加可能的选择和方式,我们能够获得所有满足条件的组合数,找到方案的总数。