我正在试图弄清楚如何解决一个常见算法问题似乎很棘手的问题,但需要额外的逻辑来处理特定要求的问题。
鉴于硬币和金额列表,我需要计算使用无限量可用硬币提取给定金额的可能方式的总数(这是一个经典的变更问题https://en.wikipedia.org/wiki/Change-making_problem很容易解决使用动态编程)也满足一些额外要求:
实施例
金额为6欧元和硬币[1,2]:解决方案为4
[(1,1), (2,2)]
[(1,1,1), (1,1,1)]
[(2,2), (1,1)]
[(1,2), (1,2)]
8欧元和硬币[1,2,6]的数量:解决方案是7
[(1,1,2), (1,1,2)]
[(1,2,2), (1,1,1)]
[(1,1,1,1), (1,1,1,1)]
[(2), (6)]
[(1,1,1), (1,2,2)]
[(2,2), (2,2)]
[(6), (2)]
到目前为止,我尝试了不同的方法,但我找到的唯一方法是收集所有可能的解决方案(使用动态编程),然后过滤不可拆分的解决方案(使用奇数个硬币)和重复。我很确定有一种组合方式来计算重复的总数,但我无法弄清楚如何。
答案 0 :(得分:1)
(以下方法首先枚举分区。我的其他答案以自下而上的方式生成分配。)如果您想根据硬币计数计算硬币交换的分割 ,并为每一方排除硬币的冗余分配(例如,将1 + 2 + 2 + 1分成相等基数的两个部分只有(1,1) | (2,2)
,(2,2) | (1,1)
或(1,2) | (1,2)
和每个部分中的元素顺序无关紧要),我们可以依赖于忽略顺序的分区的枚举。
但是,我们需要知道每个分区中多元素的元素(或类似的元素的集合),以便计算将它们分成两部分的可能性。例如,要计算拆分1 + 2 + 2 + 1
的方法,我们首先会计算出每枚硬币的数量:
Python代码:
def partitions_with_even_number_of_parts_as_multiset(n, coins):
results = []
def C(m, n, s, p):
if n < 0 or m <= 0:
return
if n == 0:
if not p:
results.append(s)
return
C(m - 1, n, s, p)
_s = s[:]
_s[m - 1] += 1
C(m, n - coins[m - 1], _s, not p)
C(len(coins), n, [0] * len(coins), False)
return results
输出:
=> partitions_with_even_number_of_parts_as_multiset(6, [1,2,6])
=> [[6, 0, 0], [2, 2, 0]]
^ ^ ^ ^ this one represents two 1's and two 2's
既然我们正在计算选择其中一半的方法,我们需要在多项式乘法中找到x^2
的系数
(x^2 + x + 1) * (x^2 + x + 1) = ... 3x^2 ...
表示从多重集计数[2,2]
中选择两种方式的三种方式:
2,0 => 1,1
0,2 => 2,2
1,1 => 1,2
在Python中,我们可以使用numpy.polymul
乘以多项式系数。然后我们在结果中查找适当的系数。
例如:
import numpy
def count_split_partitions_by_multiset_count(multiset):
coefficients = (multiset[0] + 1) * [1]
for i in xrange(1, len(multiset)):
coefficients = numpy.polymul(coefficients, (multiset[i] + 1) * [1])
return coefficients[ sum(multiset) / 2 ]
输出:
=> count_split_partitions_by_multiset_count([2,2,0])
=> 3
(发表了类似的答案here。)
答案 1 :(得分:0)
这是一个表实现,并对algrid's beautiful answer进行了一些阐述。这会在约2秒内为f(500, [1, 2, 6, 12, 24, 48, 60])
生成答案。
C(n, k, S) = sum(C(n - s_i, k - 1, S[i:]))
的简单声明表示使用n
硬币添加所有获取当前金额k
的方法。然后,如果我们将n
分成所有方式,它可以分成两部分,我们可以添加所有这些部分的所有方式,可以使用相同数量的k
个硬币。
将我们选择的硬币子集固定到缩小列表的美妙意味着任何硬币的任意组合只会被计算一次 - 它将在计算中计算,其中组合中最左边的硬币是第一个硬币我们减少的子集(假设我们以相同的方式对它们进行排序)。例如,取自[6, 24, 48]
的任意子集[1, 2, 6, 12, 24, 48, 60]
只会计入子集[6, 12, 24, 48, 60]
的总和中,因为下一个子集[12, 24, 48, 60]
不会包含6
1}}和前一个子集[2, 6, 12, 24, 48, 60]
至少有一个2
硬币。
import time
def f(n, coins):
t0 = time.time()
min_coins = min(coins)
m = [[[0] * len(coins) for k in xrange(n / min_coins + 1)] for _n in xrange(n + 1)]
# Initialize base case
for i in xrange(len(coins)):
m[0][0][i] = 1
for i in xrange(len(coins)):
for _i in xrange(i + 1):
for _n in xrange(coins[_i], n + 1):
for k in xrange(1, _n / min_coins + 1):
m[_n][k][i] += m[_n - coins[_i]][k - 1][_i]
result = 0
for a in xrange(1, n + 1):
b = n - a
for k in xrange(1, n / min_coins + 1):
result = result + m[a][k][len(coins) - 1] * m[b][k][len(coins) - 1]
total_time = time.time() - t0
return (result, total_time)
print f(500, [1, 2, 6, 12, 24, 48, 60])