魔法甜点之和:小包的新挑战
问题描述
小R不再追求甜点中最高的喜爱值,今天他想要的是甜点喜爱值之和正好匹配他的预期值 S。为了达到这个目标,他可以使用魔法棒来改变甜点的喜爱值,使其变为原来喜爱值的阶乘。每个甜点只能使用一次魔法棒,也可以完全不用。
下午茶小哥今天带来了 N 个甜点,每个甜点都有一个固定的喜爱值。小R有 M 个魔法棒,他可以选择任意甜点使用,但每个甜点只能使用一次魔法棒。他的目标是通过选择一些甜点,可能使用魔法棒,使得这些甜点的喜爱值之和恰好为 S。
请计算小R有多少种不同的方案满足他的要求。如果两种方案中,选择的甜点不同,或者使用魔法棒的甜点不同,则视为不同的方案。
解题思路
这个问题可以转化为一个典型的背包问题,并结合动态规划进行求解。问题的关键点在于如何使用魔法棒将甜点的喜爱值转换为其阶乘,并在此过程中确保结果满足预期和 S。
可以通过动态规划,定义 dp[i][j] 来表示使用前 i 个甜点,选择了 j 个魔法棒,当前得到的喜爱值之和。状态转移的核心思路是:对于每个甜点,我们有两种选择:
- 不使用魔法棒,直接将该甜点的喜爱值加入到当前的和中。
- 使用魔法棒,将该甜点的喜爱值转化为它的阶乘,并将阶乘值加入到当前的和中。
输入n:表示甜点的数量。
m:表示魔法棒的数量。
s:目标和,表示小R希望选择甜点后它们的喜爱值和为 s。
like:长度为 n 的数组,表示每个甜点的喜爱值。
代码如下
from collections import defaultdict
# 计算阶乘并保存
magic = [1] * 100
def pre():
for i in range(1, 100):
magic[i] = magic[i - 1] * i
pre() # 初始化magic数组
def solution(n, m, s, like):
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)
代码解析
首先,预处理阶乘值。由于每个甜点的喜爱值最大为 99,我们可以通过计算 1!, 2!, ..., 99! 来提前存储阶乘值,避免在每次计算时重复计算。然后使用一个 f 来存储动态规划的状态,其中 f[(a, b)] 表示使用了 a 个魔法棒后,当前的喜爱值和为 b 的方案数。初始化状态是 f[(0, 0)] = 1,即表示没有选择任何甜点且没有使用魔法棒时,当前和为 0 的方案数为 1。然后,对于每个甜点更新状态,最后,我对所有符合条件(即使用 m 个魔法棒且和为 s)的方案数,求和累加。
复杂度分析
- 时间复杂度:
O(n * m * s),其中n是甜点的数量,m是魔法棒的数量,s是目标和。我们需要遍历每个甜点并更新每个可能的状态。 - 空间复杂度:
O(n * m * s),我们使用了一个大小为m * s的字典来存储动态规划状态。
总结
在算法学习中遇到这类问题时,首先要明确状态定义,然后仔细分析每种选择的影响,设计合适的状态转移方程,最后通过动态规划来高效地计算出答案。动态规划解决的问题通常都具有“最优子结构”和“重叠子问题”的特点。通过不断练习不同类型的动态规划题目,可以逐渐培养解决复杂问题的思维方式。 同时,解决此类问题时,注意以下几点:
- 清晰定义状态:确定问题的状态,并明确每个状态的含义。
- 仔细分析状态转移:思考如何从一个状态转移到另一个状态,尤其是在做选择的时候。
- 记得边界条件:初始化状态和边界条件是确保动态规划正确性的重要一步。
- 空间优化:有时候我们不需要保留所有历史状态,可以考虑空间优化,减少内存使用。