有限的排列

时间:2012-06-29 18:33:09

标签: algorithm combinatorics

我有一个有趣的问题,我有一段时间无法应付。我们有N个字母和N对应于它们的信封,这意味着所有的字母和信封都被寻址(具有相同的排列)。任务是找到没有固定点的字母的可能排列数 - 每个字母都在信封中,与所写的字母不同。当字母(和包络)通过一些N置换来解决时,问题非常容易。然后,我们所要做的就是找到N-derange的数量(http://en.wikipedia.org/wiki/Derangement)。

但总的来说这个问题可能会更有趣。我们给出了数字N和N的数字 - 第i个数字表示第i个字母(和信封)的地址。第一个数字是0(所以我们有字母和信封用[0,...,N-1]编号)。那我们有多少紊乱?例如,我们给出了:

 5 
 1 1 2 2 3

然后答案是4,因为我们只有4种情况,当每个字母都没有相应的信封时,它们是:

2 2 1 3 1
2 2 3 1 1 
2 3 1 1 2
3 2 1 1 2

(所以我们不区分具有相同地址的字母。)

有关:

6
0 0 0 1 1 1

答案是1.因为:1 1 1 0 0 0是唯一的方法。

我差点忘了...... 1< = N< = 15就足够了。

我想知道是否有任何方法可以很快地计算它以及如何做到这一点。

我应该如何使用算法?

2 个答案:

答案 0 :(得分:2)

首次尝试,将其视为直接的动态编程问题。

所以你从左到右工作,对于信封列表中的每个位置,对于你可以留下的每个可能的字母组,找出你可以到达那个点的方式。前进很容易,你只需要一套,你知道有多少种方法可以到达那里,然后对于你可以放在下一个信封中的每个字母,你可以通过方法的数量增加结果集的总和。达到这一点。当您到达信封列表的末尾时,您将找到剩余0个字母的方式,这是您的答案。

在第二个例子中,可以按如下方式进行:

Step 0: next envelope 1
  {1: 2, 2: 2, 3: 1}: 1
    -> {1: 2, 2: 1, 3: 1}
    -> {1: 2, 2: 2}

Step 1: next envelope 1
  {1: 2, 2: 1, 3: 1}: 1
    -> {1: 2, 2: 1}
    -> {1: 2, 3: 1}
  {1: 2, 2: 2}: 1
    -> {1: 2, 2: 1}

Step 2: next envelope 2
  {1: 2, 2: 1}: 2
    -> {1: 1, 2: 1}
  {1: 2, 3: 1}: 1
    -> {1: 1, 3: 1}
    -> {1: 2}

Step 3: next envelope 2
  {1: 1, 2: 1}: 2
    -> {2: 1}
  {1: 1, 3: 1}: 1
    -> {1: 1}
    -> {3: 1}
  {1: 2}: 1
    -> {1: 1}

Step 4: next envelope 3
  {2: 1}: 2
    -> {}
  {1: 1}: 2
    -> {}
  {3: 1}: 1
    // Dead end.

Step 5:
  {}: 4

这样可以使您了解所要求的计算范围。在15,你有2 ^ 15 = 32768个可能的子集来跟踪哪个是非常可行的。在20左右的某个地方你会开始耗尽内存。

我们可以改善吗?答案是我们可以。我们的绝大部分精力用于记住,比如说,到目前为止,我们是否使用了标有8的信封和标有9的信封。但我们并不关心这一点。决定完成方式的因素不在于我们是否使用了信封8或9.而是模式。有多少个标签有x个信封和y个字母。不是哪个标签,只有多少。

因此,如果我们跟踪这些信息,我们可以在每一步抓住一个信封,信封上留有最多的信封,如果有领带,我们会选择剩下最少字母的信封(和如果仍然领带,我们真的不关心我们得到的那个)。并像以前一样进行计算,但中间状态要少得多。 (我们不会随着你的信封排成一行。但是用信封对最后的信件做一个稳定的分类,然后你就会找回你上面的信件。)

让我们使用符号[x y]: z来表示z个带有x个信封和y个字母的标签。我们有一个此类标签的列表然后您的1 1 2 2 3示例可以表示为{[2 2]: 2, [1 1]: 1}。对于过渡,我们会使用其中一个[2 2]标签,并使用另一个标签(转发给{[2 1]: 1, [1 2]: 1, [1 1]: 1})或者我们将其中一个[2 2]标签,并使用[1 1]标签中的字母(转换为{[2 2]: 1, [1 2]: 1, [1 0]: 1})。

让我们进行计算。我将列出状态,到达目的地的计数以及你所做的转变:

Step 1:
  {[2 2]: 2, [1 1]: 1}: 1
    -> 1 * {[2 1]: 1, [1 2]: 1, [1 1]: 1}
    -> 1 * {[2 2]: 1, [1 2]: 1, [1 0]: 1}

Step 2:
  {[2 1]: 1, [1 2]: 1, [1 1]: 1}: 1
    -> 1 * {[1 1]: 3}
    -> 1 * {[1 1]: 1, [1 2]: 1, [1 0]: 1}
  {[2 2]: 1, [1 2]: 1, [1 0]: 1}: 1
    -> 1 * {[1 2]: 1, [1 1]: 1, [1 0]: 1}

Step 3:
  {[1 1]: 3}: 1
       // This is 2x because once the label is chosen, there are 2 letters to use.
    -> 2 * {[0 1]: 1, [1 0]: 1, [1 1]: 1}
  {[1 1]: 1, [1 2]: 1, [1 0]: 1}: 2
    -> 1 * {[1 0]: 1, [1 2]: 1, [0 0]: 1}
    -> 1 * {[1 1]: 2, [0 0]: 1}
  {[1 2]: 1, [1 1]: 1, [1 0]: 1}: 1
    -> 1 * {[1 1]: 2, [0 0]: 1}
    -> 1 * {[1 2]: 1, [1 0]: 1, [0 0]: 1}

Step 4:
  {[0 1]: 1, [1 0]: 1, [1 1]: 1}: 2
    -> 1 * {[1 1]: 1, [0 0]: 2}
    -> 1 * {[1 0]: 1, [0 1]: 1, [0 0]: 1}
  {[1 0]: 1, [1 2]: 1, [0 0]: 1}: 2
    -> 1 * {[1 1]: 1, [0 0]: 2}
  {[1 1]: 2, [0 0]: 1}: 2
    -> 1 * {[1 0]: 1, [0 1]: 1, [0 0]: 1}

Step 5:
  {[1 1]: 1, [0 0]: 2}: 4
    // dead end
  {[1 0]: 1, [0 1]: 1, [0 0]: 1}: 4
    -> 1 * {[0 0]: 3}

所以答案是4。

这可能看起来像是一项疯狂的工作 - 远远超过枚举。它就是!

然而它会扩展。尝试使用100个字母和信封,它应该快速运行。

答案 1 :(得分:0)

15“正常”排列需要1.3 * 10 ^ 9次强力尝试,但是使用相同的键我们的排列要少得多。

让我们使用你的例子:5张牌1 1 2 2 3

设置排列数组

5 4 3 2 1

并准备通过从右到左递减来迭代它 像一个数字。忽略所有非排列的组合。

5 4 3 2 1忽略

5 4 3 2 0忽略

5 4 3 1 5忽略

...

5 4 3 1 2好

相比之下,通过更好的排列可以大大改善这一点 算法,但这不容易,我必须考虑一下。

下一步是生成您的比较。 置换数组选择一个置换:

5 4 3 1 2表示3 2 2 1 1

这将测试紊乱。有一件事会大大加快 您的比较将是您可以跳过无效组合。

如果开头5选择了错误的项目,则可以全部跳过 5 X X X X排列并继续4 5 3 2 1。

每次发现紊乱时,请增加一个计数器。 完成。