问题描述
小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
动态规划常用类型
1. 重叠子问题:
动态规划特别适合处理具有重叠子问题的情况。在这道题中,许多组合的计算会涉及到相同的子问题。例如,计算选择某些项目的组合数时,可能会多次计算相同的“喜欢”值和选择数量的组合。动态规划通过存储这些子问题的结果,避免了重复计算,从而提高了效率。
2. 最优子结构:
动态规划依赖于最优子结构的性质,即一个问题的最优解可以由其子问题的最优解构成。在这道题中,选择某个项目的组合数可以通过之前选择的项目的组合数来推导。因此,动态规划能够有效地构建出最终的解。
3. 状态空间的可管理性:
在这道题中,状态空间是有限的,主要由选择的项目数量和“喜欢”值的总和构成。动态规划通过定义状态(如 f[(a, b)])来管理这些组合,使得问题的复杂性得以控制。相比于其他方法(如暴力搜索),动态规划能够在合理的时间内找到解。
4. 时间复杂度的优化:
动态规划通常能够将问题的时间复杂度从指数级降低到多项式级。在这道题中,使用动态规划可以有效地减少计算量,使得即使在较大的输入规模下也能在可接受的时间内得到结果。
5. 清晰的逻辑结构:
动态规划提供了一种清晰的逻辑结构,使得问题的解决过程易于理解和实现。通过定义状态、状态转移和结果计算,代码的可读性和可维护性得以提高。
6. 适应性强:
动态规划可以灵活地适应不同的约束条件和目标。在这道题中,动态规划能够处理选择数量的限制(m)和目标和的限制(s),使得算法能够适应多种情况。
题解
from collections import defaultdict
def precompute_magic():
magic = [1] * 100
for i in range(1, 100):
magic[i] = magic[i - 1] * i
return magic
def solution(n, m, s, like):
magic = precompute_magic()
f = defaultdict(int)
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_result = sum(f[(i, s)] for i in range(m + 1))
return sum_result
动态规划的基本步骤
-
定义状态: 在动态规划中,首先需要定义一个状态来表示问题的某个特定情况。在这段代码中,状态
f[(a, b)]表示选择了a个项目,并且这些项目的“喜欢”值的总和为b的组合数。 -
初始化状态: 在开始动态规划之前,需要初始化状态。在代码中,
f[(0, 0)] = 1表示没有选择任何项目时,组合数为 1。这是动态规划的基础状态。 -
状态转移: 状态转移是动态规划的核心,指的是如何从已知状态推导出未知状态。在这段代码中,状态转移的过程如下:
- 对于每个项目
i,我们考虑将其加入到当前的组合中。 - 复制当前状态
g = f.copy(),以便在更新时不影响正在迭代的状态。 - 遍历当前状态
g中的每个组合(a, b)和其对应的值v。 - 对于每个组合,检查是否可以将当前项目的“喜欢”值
like[i - 1]添加到组合中。如果添加后不超过目标和s,则更新状态f[(a, b + like[i - 1])]。 - 还需要检查是否可以将当前项目的阶乘值
magic[like[i - 1]]添加到组合中,同时确保选择的项目数量不超过m,并且和不超过s。如果条件满足,则更新状态f[(a + 1, b + magic[like[i - 1]])]。
- 对于每个项目
-
计算结果: 在所有项目都处理完后,最终的结果是所有选择数量在
0到m之间且和为s的组合数的总和。通过遍历状态f中的所有可能的选择数量,计算出符合条件的组合数。 -
返回结果: 最后,返回计算得到的结果。