Algorithm 具有给定约束的分区数
考虑一组13个丹麦人、11个日本人和8个波兰人。众所周知,将这组人划分为组的不同方式的数目是13+11+8=32:th Bell数(集合分区的数目)。然而,我们被要求在给定的约束下找到可能的集合分区的数量。问题如下: 如果一个集合分区没有至少由两个人组成的只包含一个国籍的组,则称其为好。此集合有多少个好的分区?(一个小组只能包括一个人。) 蛮力方法需要遍历大约10^26个分区并检查哪些分区是好的。这似乎是不可行的,尤其是如果群体规模较大或有人介绍了其他国籍。有没有聪明的办法Algorithm 具有给定约束的分区数,algorithm,partitioning,combinatorics,Algorithm,Partitioning,Combinatorics,考虑一组13个丹麦人、11个日本人和8个波兰人。众所周知,将这组人划分为组的不同方式的数目是13+11+8=32:th Bell数(集合分区的数目)。然而,我们被要求在给定的约束下找到可能的集合分区的数量。问题如下: 如果一个集合分区没有至少由两个人组成的只包含一个国籍的组,则称其为好。此集合有多少个好的分区?(一个小组只能包括一个人。) 蛮力方法需要遍历大约10^26个分区并检查哪些分区是好的。这似乎是不可行的,尤其是如果群体规模较大或有人介绍了其他国籍。有没有聪明的办法 编辑:作为旁注。可能
编辑:作为旁注。可能没有希望找到一个真正好的解决方案。一位备受尊敬的组合数学专家提出了一个相关的问题,我认为,基本上说,相关的问题,因此这个问题,很难精确地解决。精确的解析解很难,但多项式时间+空间动态规划解很简单 首先,我们需要对组的大小进行绝对排序。我们通过比较我们有多少丹麦人、日本人和波兰人来做到这一点 接下来,要编写的函数就是这个函数
m is the maximum group size we can emit
p is the number of people of each nationality that we have left to split
max_good_partitions_of_maximum_size(m, p) is the number of "good partitions"
we can form from p people, with no group being larger than m
显然,您可以将其编写为一个有点复杂的递归函数,它总是选择要使用的下一个分区,然后用它作为新的最大大小调用自己,并从p中减去分区。如果你有这个函数,那么你的答案就是max\u good\u partitions\u of\u maximum\u size(p,p)
withp=[13,11,8]
。但这将是一次暴力搜索,不会在合理的时间内进行
最后,通过缓存对该函数的每个调用来应用,它将在多项式时间内运行。但是,您还必须缓存多项式数量的调用。这里有一个使用动态编程的解决方案 它从一个空集开始,然后一次添加一个元素并计算所有有效分区 状态空间是巨大的,但请注意,为了能够计算下一步,我们只需要了解分区的以下情况:
- 对于每个国籍,它包含多少只由该国籍的一名成员组成的集合。(例如:{a})
- 它包含多少个包含混合元素的集合。(例如:{a,b,c})
[0, 1, 2, 2] -> 3
{a}{b}{c}{mixed}
e.g.: 3 partitions that look like: {b}, {c}, {c}, {a,c}, {b,c}
以下是python中的代码:
import collections
from operator import mul
from fractions import Fraction
def nCk(n,k):
return int( reduce(mul, (Fraction(n-i, i+1) for i in range(k)), 1) )
def good_partitions(l):
n = len(l)
i = 0
prev = collections.defaultdict(int)
while l:
#any more from this kind?
if l[0] == 0:
l.pop(0)
i += 1
continue
l[0] -= 1
curr = collections.defaultdict(int)
for solution,total in prev.iteritems():
for idx,item in enumerate(solution):
my_solution = list(solution)
if idx == i:
# add element as a new set
my_solution[i] += 1
curr[tuple(my_solution)] += total
elif my_solution[idx]:
if idx != n:
# add to a set consisting of one element
# or merge into multiple sets that consist of one element
cnt = my_solution[idx]
c = cnt
while c > 0:
my_solution = list(solution)
my_solution[n] += 1
my_solution[idx] -= c
curr[tuple(my_solution)] += total * nCk(cnt, c)
c -= 1
else:
# add to a mixed set
cnt = my_solution[idx]
curr[tuple(my_solution)] += total * cnt
if not prev:
# one set with one element
lone = [0] * (n+1)
lone[i] = 1
curr[tuple(lone)] = 1
prev = curr
return sum(prev.values())
print good_partitions([1, 1, 1, 1]) # 15
print good_partitions([1, 1, 1, 1, 1]) # 52
print good_partitions([2, 1]) # 4
print good_partitions([13, 11, 8]) # 29811734589499214658370837
它为测试用例生成正确的值。我还用蛮力解(对于小值)对它进行了测试,它产生了相同的结果。正如你在那里问的那样,得到了你的答案,你可以在这里关闭它。由于表达式中存在一些非线性因素,生成函数很难应用。获得它的洞察力是非常好的,它告诉了一些关于这个问题的事情。然而,不是我想要的确切数字。讨论生成函数的好处。实际上,获得这些数字需要一个聪明的算法(或荒谬的计算能力)。很可能没有什么比暴力更好的了。我把这篇文章贴在这里,是想看看它是否能为比我更擅长算法的人敲响警钟?