Это решение не использует динамическое программирование, но значительно быстрее:
import math
from functools import reduce
def num_seq_sum(m, nums):
if m == 0:
return 1
# Avoid repeated numbers
nums = list(set(nums))
# Begin with no numbers picked
base = [0] * len(nums)
return sum(_seqs_sum_rec(m, nums, base, 0))
def _seqs_sum_rec(m, nums, current, i):
if i >= len(nums):
raise StopIteration
# Try without adding current number, add next numbers
yield from _seqs_sum_rec(m, nums, current, i + 1)
# Try adding current number
n = nums[i]
# While we can fit more copies of current number
while m > n:
current[i] += 1
m -= n
yield from _seqs_sum_rec(m, nums, current, i + 1)
# If we can fit exactly one more
if m == n:
current[i] += 1
# Number of permutations of the current combination
yield _num_permutations(current)
# Undo additions for parent call
current[i] = 0
def _num_permutations(comb):
return math.factorial(sum(comb)) // reduce(lambda a, b: a * b, (math.factorial(c) for c in comb), 1)
nums = [3, 5, 8, 13]
m = 13
print(RecursiveCount(m, nums) == num_seq_sum(m, nums))
# True
Небольшой тест производительности:
nums = [1, 3, 5, 8, 10, 15]
m = 30
%timeit RecursiveCount(m, nums)
# 1.48 s ± 6.86 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit num_seq_sum(m, nums)
# 4.77 ms ± 85.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)