Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Algorithm 有限制的置换_Algorithm_Combinatorics - Fatal编程技术网

Algorithm 有限制的置换

Algorithm 有限制的置换,algorithm,combinatorics,Algorithm,Combinatorics,我有一个有趣的问题,一段时间内我无法解决。我们有N个字母和N个对应的信封,这意味着所有的字母和信封都有地址(具有相同的排列)。任务是找出没有固定点的字母的可能排列数量——每个字母都在信封中,信封的地址不同于此字母的地址。当字母(和信封)通过某种N-置换进行寻址时,问题就很容易解决了。那么我们所要做的就是找到N-错乱的数目() 但总的来说,这个问题可能更有趣。我们得到了N个数字和N个数字——第i个数字表示第i个字母(和信封)的地址。第一个数字是0(因此我们有编号为[0,…,N-1]的信件和信封)。

我有一个有趣的问题,一段时间内我无法解决。我们有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}