在这篇笔记中,我将通过分析一道有关甜点喜爱值的组合问题,分享我的学习心得与方法。这道题目来自豆包MarsCode AI的刷题平台,题目涉及到一个在给定条件下,如何通过选择甜点并使用魔法来达到某个特定喜爱值之和。以下是题目的详细解析和我在解决过程中的思路总结。
问题描述
题目中有 N 个甜点,每个甜点有一个固定的喜爱值。你有 M 个魔法棒,每个魔法棒可以将某个甜点的喜爱值变为其阶乘。你的目标是选择一些甜点,可能使用魔法棒,将它们的喜爱值之和恰好为目标值 S。每个甜点只能使用一次魔法棒,且魔法棒可以用于任意甜点或者完全不使用。
问题的核心思想
这个问题本质上是一个 背包问题,但与传统的背包问题不同的是,你不仅要考虑每个甜点的原始喜爱值,还要考虑将每个甜点的喜爱值变为阶乘后的情况。每个甜点可以选择两种状态:一种是保持原值,另一种是变为其阶乘值。我们需要通过选择不同的甜点,并决定是否使用魔法棒,来满足总和为 S。
解题思路
在本题中,我们使用 动态规划 来解决。具体来说,定义一个状态 f[(i, j)],表示使用了 i 个魔法棒,且当前甜点总喜爱值为 j 的方案数。这样的问题可以通过以下步骤进行求解:
-
状态转移:
- 如果我们选择当前的甜点原始值
like[i],则该状态转移为f[(i, j + like[i])]。 - 如果我们选择使用魔法棒将当前甜点的喜爱值转为其阶乘值
magic[like[i]],则该状态转移为f[(i + 1, j + magic[like[i]])]。
- 如果我们选择当前的甜点原始值
-
初始化:
f[(0, 0)] = 1,表示未选择任何甜点时,喜爱值为0的方案只有 1 种。
-
遍历所有甜点:对于每个甜点,考虑两种情况:
- 直接使用它的原始喜爱值。
- 使用魔法棒将其喜爱值转化为阶乘值。
-
返回结果:最终,我们需要计算使用不超过
M个魔法棒,并且总和为S的方案数。
代码实现
python复制代码
from collections import defaultdict
# 计算阶乘的魔法数组
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__":
# 你可以在这里添加更多的测试案例
print(solution(3, 2, 6, [1,2,3]) == 5 )
print(solution(3, 1, 1, [1,1,1]) == 6 )
代码详解
-
阶乘预处理:首先,我们用
pre()函数预处理了一个magic数组,它存储了从1到99的阶乘值。因为阶乘增长非常快,这个数组的大小和范围都设置得适合题目的输入限制。 -
动态规划数组:我们使用
f[(a, b)]来记录不同状态下的方案数,其中a表示使用了多少个魔法棒,b表示当前的甜点喜爱值之和。使用defaultdict(int)来避免在访问不存在的键时出现错误。 -
状态转移:对于每个甜点,我们从备份状态
g中遍历每一种可能的状态转移:- 直接加上当前甜点的喜爱值
like[i-1]。 - 使用魔法棒将当前甜点的喜爱值转化为阶乘值
magic[like[i-1]]。
- 直接加上当前甜点的喜爱值
-
最终结果:遍历所有可能使用的魔法棒数量
i,累加所有使得总喜爱值恰好为S的方案数。
学习总结
在这道题目中,我再次深刻感受到了 动态规划 的强大。通过状态压缩和备份,能够有效地解决这个问题。尤其是对于类似背包问题的变种,我们需要充分考虑每个物品的不同选择,尤其是一些特殊操作(如阶乘)的影响。
- 高效的状态管理:动态规划中状态的选择和管理非常重要。在这道题中,我们通过备份当前状态并进行遍历,确保了每种可能的方案都能被考虑到,避免了重复计算。
- 阶乘的快速增长:阶乘增长非常快,因此我们通过预处理将其存储起来,减少了重复计算。
- 错误处理与边界条件:题目中对魔法棒的数量和喜爱值总和做了限制,因此在代码中需要仔细处理每个状态转移时的边界条件,确保不会出现越界的情况。
对其他同学的学习建议
- 多做动态规划题目:动态规划题目通常涉及到状态压缩、备份、转移等技巧,多做一些类似题目能够加深对该算法的理解。
- 阶乘和指数问题:面对阶乘、指数等快速增长的函数时,务必考虑预处理和范围控制。
- 复盘错误:如果在学习过程中遇到不懂的部分,可以尝试从更简单的案例入手,逐步增加复杂度,复盘和总结错误,帮助自己加深理解。
通过不断练习和总结,我逐渐掌握了动态规划的精髓,也在不断提升自己解题的效率和能力。希望我的笔记对大家有所帮助!