我有一个有趣的问题,我有一段时间无法应付.我们有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个字母和信封,它应该快速运行.