【问题标题】:Number of restricted arrangements限制安排的数量
【发布时间】:2021-12-15 05:37:21
【问题描述】:

我正在寻找一种更快的方法来解决这个问题:

假设我们有 n 个盒子n 个弹珠(每个都有不同的种类)。 每个盒子只能装一些弹珠(如下例所示),一个盒子只能装一颗弹珠请阅读编辑内容。整个算法已在下面链接的帖子中进行了描述,但没有准确描述,因此我要求重新解释。

问题是:在多项式时间内,我可以通过多少种方式将弹珠放入盒子中?

例子:

n=3

Marbles: 2,5,3

Restrictions of the i-th box (i-th box can only contain those marbles): {5,2},{3,5,2},{3,2}

The answer: 3, because the possible positions of the marbles are: {5,2,3},{5,3,2},{2,5,3}

我有一个在 O(2^n) 中有效的解决方案,但它太慢了。盒子公差也有一个限制,我觉得这不是很重要,但我也会写出来。每个盒子都有自己的种类限制,但有一个种类列表被所有人接受(在上面的例子中,这个被广泛接受的种类是 2)。

编辑:我刚刚发现了这个问题,但我不确定它是否适用于我的情况,并且动态解决方案没有得到很好的描述。有人可以澄清一下吗?这个问题是4年前回答的,所以我不会在那里问。 https://math.stackexchange.com/questions/2145985/how-to-compute-number-of-combinations-with-placement-restrictions?rq=1

编辑#2:我还必须提到,除了广泛接受的列表之外,一个盒子的接受列表的最大大小有 0 1 或 2 个元素。

Edit#3:这个问题是指我之前的问题(Allowed permutations of numbers 1 to N),我觉得这个问题太笼统了。我附上这个链接是因为还有一个更重要的信息 - 可以放置弹珠的盒子之间的距离不高于 2。

【问题讨论】:

  • 有些事情我不清楚。如果在您的示例中,如果大理石“5”可以放入框 1 和 2,那么条件“只有一个弹珠适合一个盒子”?如果框 3 只允许弹珠 3 和 2,那么它的顺序是否重要?
  • @Ripi2 这个条件只是说你不能把 2 个弹珠放在一个盒子里。这些盒子限制只是说明你可以在这个地方放哪些弹珠。我不明白你问题的第二部分。你的意思是什么顺序?
  • 将 n 个不同的弹珠中的每一个放在 n 个盒子的每一个中都有n! 解决方案。限制会降低这个数字,比如对(n-k)! 说一些k。但它的复杂性仍然是阶乘而不是多项式。
  • @Ripi2 是的,所以问题是找到这个 k,因为我只需要数字,而不是所有的组合。我还发现了一个表达式,可以让我解决这个问题,但它有时会跳过某些情况
  • 链接的线程很难拼凑起来。但是,鉴于您第二次编辑中的信息,我相信这可以使用FKT algorithm 在多项式时间内解决。如果您选择“全部接受”的弹珠和某个框 B,并将它们从图中删除,则剩余的图不包含 K_3,3 作为子图,因此 FKT 算法应该对其进行计算以计算完美匹配,重复此操作初始框 B 的每个选择的过程。

标签: algorithm combinations combinatorics


【解决方案1】:

正如 cmets 中所指出的,https://cs.stackexchange.com/questions/19924/counting-and-finding-all-perfect-maximum-matchings-in-general-graphs 就是这个问题,有关于如何解决它的论文的链接,并且计算匹配的数量是 #P-complete。我会建议找到那些论文。

至于动态规划,只需写一个递归解,然后记忆即可。 (这是自上而下的,并且几乎总是更容易的方法。)对于具有固定(且相当少)数量的盒子的堆栈交换问题,该方法是可以管理的。不幸的是,在您使用大量框的变体中,天真的递归版本看起来像这样(未经测试,可能有问题):

def solve (balls, box_rules):
    ball_can_go_in = {}
    for ball in balls:
        ball_can_go_in[ball] = set()
    for i in range(len(box_rules)):
        for ball in box_rules[i]:
            ball_can_go_in[ball].add(i)

    def recursive_attempt (n, used_boxes):
        if n = len(balls):
            return 1
        else:
            answer = 0
            for box in ball_can_go_in[balls[n]]:
                if box not in used_boxes:
                    used_boxes.add(box)
                    answer += recursive_attempt(n+1, used_boxes)
                    used_boxes.remove(box)
             return answer

    return recursive_attempt(0, set())

为了记住它,你必须构造新的集合,也许使用位串,但是你会发现你用 n 个事物的子集来调用它。它们的数量是指数级的。不幸的是,这将花费指数时间并使用指数内存。

如果你用 LRU 缓存替换记忆层,你可以控制它使用多少内存,并且可能仍然从记忆中获得一些好处。但最终你仍然会使用指数级或更糟的时间。

如果你走这条路,一个实用的技巧是根据球进入的盒子数量对球进行分类。你希望从尽可能少的选择开始。由于这是试图降低指数复杂性,因此在此排序步骤上值得做很多工作。所以我会首先选择进入最少盒子的球。然后,我接下来会选择进入最少新盒子的球,并以最少的整体打破平局。第三个球将是最少的新盒子,以最少的未使用的盒子打破平局,以最少的盒子打破平局。以此类推。

我们的想法是尽早产生和发现强制选择和冲突。事实上,这非常重要,值得在每一步都进行搜索,以尝试发现和记录已经可见的强制选择和冲突。这感觉违反直觉,但确实有所作为。

但是,如果您执行所有这些操作,适用于 5 个框的动态编程方法将变得更快,但您仍然只能处理比简单解决方案稍大的问题。所以去看看研究,寻找比这种动态编程方法更好的想法。

(顺便说一句,包含-排除方法对每个子集都有一个术语,因此它也会呈指数级增长。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-25
    • 2011-07-25
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    相关资源
    最近更新 更多