Algorithm 有限制的置换
我有一个有趣的问题,一段时间内我无法解决。我们有N个字母和N个对应的信封,这意味着所有的字母和信封都有地址(具有相同的排列)。任务是找出没有固定点的字母的可能排列数量——每个字母都在信封中,信封的地址不同于此字母的地址。当字母(和信封)通过某种N-置换进行寻址时,问题就很容易解决了。那么我们所要做的就是找到N-错乱的数目() 但总的来说,这个问题可能更有趣。我们得到了N个数字和N个数字——第i个数字表示第i个字母(和信封)的地址。第一个数字是0(因此我们有编号为[0,…,N-1]的信件和信封)。那我们有多少精神错乱?例如,我们得到:Algorithm 有限制的置换,algorithm,combinatorics,Algorithm,Combinatorics,我有一个有趣的问题,一段时间内我无法解决。我们有N个字母和N个对应的信封,这意味着所有的字母和信封都有地址(具有相同的排列)。任务是找出没有固定点的字母的可能排列数量——每个字母都在信封中,信封的地址不同于此字母的地址。当字母(和信封)通过某种N-置换进行寻址时,问题就很容易解决了。那么我们所要做的就是找到N-错乱的数目() 但总的来说,这个问题可能更有趣。我们得到了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
(因此,我们不区分地址相同的字母)
用于:
答案是1。因为:1 1 0 0 0是唯一的方法
我差点忘了。。115个“正常”排列需要1.3*10^9次强力尝试,但使用相同的键,我们剩下的排列要少得多
让我们用你的例子:5张卡配1 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 1 1
这将测试你的精神错乱。有一件事会大大加快速度
你可以跳过无效的组合
如果5在开始时选择了错误的项目,您可以跳过所有项目
5 X X X排列并继续4 5 3 2 1
每次你发现精神错乱时,增加一个计数器。
完成。第一次尝试,将其视为一个简单的动态规划问题 因此,从左到右,对于信封列表中的每个位置,对于可能留下的每一组字母,计算出到达该点的方法的数量。前进很容易,你只需要取一个集合,你知道有多少种方法可以到达那里,然后对于你可以放入下一个信封的每一封信,你将得到的集合的总和乘以到达该点的方法的数量。当你到达信封列表的末尾时,你会发现有多少种方法可以让你剩下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
字母。我们有一个这样的标签列表,那么你的1233
示例可以表示为{[22]:2[11]:1}
。对于转换,我们将使用一个[22]
标签,或者使用另一个标签的字母(给我们转换到{[21]:1[12]:1[11]:1}
),或者我们将使用一个[22]
标签,并使用[11]
标签中的字母(给我们转换到{[22]:1[12]:1[10]: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封信和信封试试,它应该运行得很快。尽管这个问题本身很有趣,但我似乎不知道它可以应用到哪里。你有这样一个算法的使用例子吗?没有。。我喜欢算法和编写它们。这个想法确实引起了我的兴趣,但我无法解决它,所以我在这里寻求帮助。你是否在寻找一种比仅迭代有效排列更快的解决方案?@mbeckish,是的。。但我甚至不知道这是否可能,我只是想知道。我希望它能适用于我不完全理解第一种动态方法的情况。你能详细说明这两个例子中的一个是如何工作的吗?只是开始的几步。我会非常感激的。@xan我用动态方法做了一个例子,这样你就可以看到它是如何工作的。@billy,我非常感谢你
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}