题目描述
小R不再追求甜点中最高的喜爱值,今天他想要的是甜点喜爱值之和正好匹配他的预期值 S。为了达到这个目标,他可以使用魔法棒来改变甜点的喜爱值,使其变为原来喜爱值的阶乘。每个甜点只能使用一次魔法棒,也可以完全不用。
下午茶小哥今天带来了 N 个甜点,每个甜点都有一个固定的喜爱值。小R有 M 个魔法棒,他可以选择任意甜点使用,但每个甜点只能使用一次魔法棒。他的目标是通过选择一些甜点,可能使用魔法棒,使得这些甜点的喜爱值之和恰好为 S。
请计算小R有多少种不同的方案满足他的要求。如果两种方案中,选择的甜点不同,或者使用魔法棒的甜点不同,则视为不同的方案。
解题思路
这个问题可以通过 动态规划(DP)来高效求解。每个甜点有两种选择:不使用魔法棒,或者使用魔法棒将其喜爱值变为阶乘。因此,对于每个甜点的选择,我们可以通过动态规划来逐步更新可能的方案数。
1. 状态表示
我们用一个动态规划的字典 f 来表示不同状态。字典的键 (a, b) 表示已经选择了 a 个甜点且它们的喜爱值总和为 b。值 v 表示达到该状态的方案数。
- 初始状态:
f[(0, 0)] = 1,表示没有选择任何甜点时,喜爱值和为0的方案数为 1。
2. 选择情况
每个甜点有两种可能的状态:
- 不使用魔法棒,选择该甜点的原始喜爱值。
- 使用魔法棒,选择该甜点的阶乘值。
我们遍历每个甜点,更新状态。
3. 状态转移
对于每一个状态 (a, b),我们可以:
- 不使用魔法棒,更新为
(a, b + like[i])。 - 使用魔法棒,更新为
(a + 1, b + magic[like[i]]),其中magic[i]为该甜点的阶乘。
4. 最终答案
最后,我们统计所有满足条件的状态 (a, s),其中 a 的范围是 0 到 m,s 是目标值。
代码实现
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__":
# You can add more test cases here
print(solution(3, 2, 6, [1, 2, 3]) == 5) # 预期输出:5
print(solution(3, 1, 1, [1, 1, 1]) == 6) # 预期输出:6
代码解析
-
阶乘预处理:
- 使用
magic数组来存储 1 到 99 的阶乘,提前计算并存储,提高效率。
- 使用
-
动态规划表
f:- 使用
defaultdict(int)来保存状态,其中键(a, b)代表选择了a个甜点,喜爱值和为b的方案数。
- 使用
-
状态更新:
- 对每个甜点,检查是否能够通过不使用魔法棒或者使用魔法棒更新当前的状态。
- 使用
f.copy()备份当前状态,避免在遍历时修改正在更新的状态。
-
返回结果:
- 最后统计所有满足条件的状态
(a, s),其中a可能从 0 到m,累加得到最终的方案数。
- 最后统计所有满足条件的状态
测试与输出
print(solution(3, 2, 6, [1, 2, 3]) == 5)
print(solution(3, 1, 1, [1, 1, 1]) == 6)
- 第一个测试:
solution(3, 2, 6, [1, 2, 3])返回 5,表示有 5 种方法可以使得所选甜点的喜爱值和为 6。 - 第二个测试:
solution(3, 1, 1, [1, 1, 1])返回 6,表示有 6 种方法可以使得所选甜点的喜爱值和为 1。
时间与空间复杂度
- 时间复杂度:
O(n * m * s),其中n是甜点数量,m是魔法棒的数量,s是目标总和。每次更新状态都需要遍历当前所有状态,因此复杂度为三者的乘积。 - 空间复杂度:
O(n * s),由于我们需要记录所有可能的(a, b)状态。
个人思考与优化
-
动态规划的优化:
- 当前方法通过复制
f来备份状态,这样可以确保每次更新时不会相互干扰。但在实际中,完全可以通过逆序更新状态来减少空间开销,即直接从后向前更新f数组。
- 当前方法通过复制
-
阶乘增长问题:
- 随着
like[i]值的增大,阶乘的值会迅速增长,这可能导致数值溢出或计算时间变长。可以通过限制like[i]的最大值来避免这一问题,或者直接检查是否可能超出目标s。
- 随着
-
性能提升:
- 如果
s值非常大,可以考虑使用压缩存储的动态规划方法,例如只存储需要的状态,而不是维护整个二维数组。
- 如果