【问题标题】:Number of partitions with a given constraint具有给定约束的分区数
【发布时间】:2015-11-30 13:04:43
【问题描述】:

考虑一组 13 名丹麦人、11 名日本人和 8 名波兰人。众所周知,将这组人分成组的不同方式的数量是13+11+8=32:第贝尔数(集合分区的数量)。然而,我们被要求在给定的约束下找到可能的集合分区的数量。问题如下:

如果集合分区没有由至少两个人组成的组包含单一国籍,则称该分区为good。这组有多少个分区? (一组可能只有一个人。)

蛮力方法需要遍历大约 10^26 个分区并检查哪些是好的。这似乎是不可行的,特别是如果群体较大或引入其他国籍。有没有更聪明的方法?

编辑:作为旁注。一个非常好的解决方案可能没有希望。一位受人尊敬的组合学专家answered 一个相关问题,我认为这基本上是说相关问题,因此这个问题也很难准确解决。

【问题讨论】:

  • 你也在那里问过并得到了答案 IMO 你可以在这里关闭它
  • 由于表达式中有一些非线性的东西,生成函数很难应用。获得它的洞察力非常好,它告诉了一些关于这个问题的事情。但是,不是我正在寻找的确切数字。
  • This 讨论了生成函数的好处。实际上获得数字需要一个聪明的算法(或荒谬的计算能力)。
  • 很可能没有什么比蛮力更好的了。我在这里发布这个,看看它是否会为比我更擅长算法的人敲响警钟。

标签: algorithm partitioning combinatorics


【解决方案1】:

这是一个使用动态规划的解决方案。

它从一个空集开始,然后一次添加一个元素并计算所有有效分区。

状态空间是巨大的,但请注意,为了能够计算下一步,我们只需要了解以下有关分区的信息:

  • 对于每个国籍,它包含多少只包含该国籍的单个成员的集合。 (例如:{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

它为测试用例生成正确的值。我还针对蛮力解决方案(对于小值)对其进行了测试,它产生了相同的结果。

【讨论】:

    【解决方案2】:

    精确的解析解很难,但多项式时间+空间动态规划解很简单。

    首先,我们需要对组的大小进行绝对排序。我们通过比较我们有多少丹麦人、日本人和波兰人来做到这一点。

    接下来要写的函数就是这个了。

    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_good_partitions_of_maximum_size(p, p)p = [13, 11, 8]。但这将是一个无法在合理时间内运行的蛮力搜索。

    最后通过缓存对该函数的每次调用来应用https://en.wikipedia.org/wiki/Memoization,它将在多项式时间内运行。但是,您还必须缓存多项式调用次数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-13
      • 2017-05-22
      相关资源
      最近更新 更多