algorithm – 有限制的排列

我有一个有趣的问题,我有一段时间无法应付.我们有N个字母和N对应于它们的信封,这意味着所有的字母和信封都被寻址(具有相同的排列).任务是找到没有固定点的字母的可能排列数 – 每个字母都在信封中,与所写的字母不同.当字母(和包络)通过一些N置换来解决时,问题非常容易.然后我们要做的就是找到N-derangements的数量(
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就足够了. 我想知道是否有任何方法可以很快地计算它以及如何做到这一点. 我该如何处理算法?

最佳答案 第一次尝试,将其视为一个简单的动态编程问题.

因此,对于信封列表中的每个位置,您可以从左到右工作,对于您可以留下的每个可能的字母集,找出可以达到该点的方式的数量.前进很容易,你只需要一套,你知道有多少种方法可以到达那里,然后对于你可以放在下一个信封中的每个字母,你可以通过方法的数量增加结果集的总和.达到这一点.当你到达信封列表的末尾时,你会发现有多少种方法可以留下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来表示存在带有x个包络和y个字母的z个标签.我们有一个这样的标签列表然后你的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个字母和信封,它应该快速运行.

点赞