【问题标题】:"K-transformed" permutations“K 变换”排列
【发布时间】:2011-08-25 21:25:36
【问题描述】:

几天来,我一直在努力解决这个问题,并在网上彻底搜索了有关如何解决它的任何提示。如果您喜欢面向数学的编程问题,请看一看!

这里是problem (PDF courtesy of UVA)

考虑一个由 n 个整数组成的序列 。由于所有的值都是不同的,我们知道有 n 个阶乘排列。如果每个元素的原始位置和新位置之间的绝对差最大为 K,则称为 K 变换排列。给定 n 和 K,您必须找出 K 变换排列的总数。

...

输入: 输入的第一行是一个整数 T (T

输出: 对于每个案例,首先输出案例编号,然后是所需的结果。由于结果可能很大,所以输出结果模 73405。

问题设置者Sohel Hafiz 将此问题归类为“Fast Matrix Exponentiation”。不幸的是,我在这里链接的谷歌搜索似乎没有显示任何相关链接,除了一个充满数学术语和符号的维基百科页面(我已经证明,维基百科是任何数学教科书的糟糕替代品)。

这是我到目前为止所做的:

此代码将通过递归计算低 n 和 k 值的 K 变换排列的数量,但过于复杂。建立一个用于搜索模式的表格就足够了:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int permute(int * a, int size, int i, int k)
{
  int j;
  int total = 0;
  int x = size-i;
  int low=0;
  int high=size;
  if (i == 0)
  {
/*    for (j=0;j<size;j++)
      printf("%d, ", a[j]);
    printf("\n");
*/    return 1;
  }
  if (x-k>0)
    low = x-k;
  if (x+k+1<size)
    high = x+k+1;
  for (j=low;j<high;j++)
  {
    int b[size];
    memcpy(b,a,size*sizeof(int));
    if (b[j] == 0)
    {
      b[j] = x+1;
      total += permute(b,size,i-1,k);
    }
  }
  return total;
}

int main() 
{
  int n, k, j, y, z;
  int * arr;
  /*scanf("%d %d", &n,&k);*/ k=2;
  for (n=0;n<14;n++)
  {
    int empty[n];
    for (j=0;j<n;j++)
      empty[j] = 0;
    arr = empty;  
    z = permute(arr, n, n, k);
    y = magic(n,k);
    printf("%d %d\n",z, y);
  }
  return 0;
}

我首先想到的是 k=1 显然是斐波那契数列。 main 中的魔法函数是我后来发现的,几乎是偶然的。它仅适用于 k=2,但精确到 n=14。

int magic(int n, int k)
{
  if (n<0)
    return 0;
  if (n==0)
    return 1;
  if (n==1)
    return 1;
  return 2*magic(n-1,k) + 2*magic(n-3,k) - magic(n-5,k);  
}

很奇怪!我不知道这个函数的意义,但它可以被简化为在一个循环中运行,以便运行得足够快,以完成 K=2 的值高达 10^9。

剩下的就是找到一个非递归方程,它可以在合理的时间内(10 秒以内)找到 K=3 的任何值。

编辑:我对用于在合理时间内解决任何给定 n 和 k 问题的算法感兴趣。我不希望任何人通过根据比赛规则的规范编写代码来真正确认他们的算法是否有效,我在答案中寻找的是关于如何解决问题并应用数值方法来达到解决方案的描述。

【问题讨论】:

  • @MAK:我认为鉴于标题中的明确声明以及 OP 已经付出了一些努力自己寻找解决方案的事实,这不算作弊。有时了解给你的解决方案确实有助于学习新的角度来看待问题。
  • @MAK 好点,问题的最后一行写得很仓促。我已经替换它以澄清我在答案中寻找的内容(当时我没有意识到解决方案的复杂性)。到目前为止,MizardX 有最好的答案并且它没有放弃解决方案。一旦我实施他的矩阵算法以确认它在合理的时间内工作,我计划奖励他。我在犹豫是否要在 C 中尝试,所以我必须用 Java 重写它。
  • @Tozar:太好了。我已经删除了我的 cmets。祝你好运。

标签: algorithm math complexity-theory permutation


【解决方案1】:

这个问题有两个部分。首先是找出k=0, 1, 2, 3 的递归公式。第二部分是使用递归公式计算大n 的值。

第一部分:

p(n, k)n 元素的排列数,这些元素经过k 变换。 当k=0 时,重复出现只是p(n, 0) = 1

对于k=1,首先考虑1 在排列中的位置。它位于位置 1 或位置 2。如果它位于位置 1,则其余元素只是 n-1 元素的原始问题。所以我们有p(n, 1) = p(n-1, 1) + ...。如果第一个元素位于位置 2,那又是什么呢?在这种情况下,第二个元素必须进入位置 1。此时您遇到了 n-2 元素的原始问题。所以递归是p(n, 1) = p(n-1, 1) + p(n-2, 1),这是斐波那契数列。

对于k=2k=3,您可以获得更多可能性,但推理是相同的。 [仍在计算确切的重复次数]

第二部分是计算n 的大值的递归解决方案。为此,您将递归转换为矩阵形式并重复平方/乘法以获得矩阵的高幂。对于k=1 的情况,矩阵为:

A = [0 1]
    [1 1]

要获得p(n + 2, 1),您需要计算A^n * [1, 1]

【讨论】:

  • 这个答案的唯一问题是缺少 k=3 的信息。 k 的较高值具有复杂的递归关系(看看我的问题中 k=2 的关系)!
  • k = 3 没有简单的递归公式...检查我的答案
【解决方案2】:

由于 K 很低,它自然落在矩阵求幂上。

每个项目最多可以从它的开始位置结束 K 个位置。这最多提供 (2 K - 1) 个位置,但某些位置可能已经被占用。放置物品时,您可以有 22 K - 1 个可能的配置(最近的 (2 K -1) 个插槽)。任何未占用位置的每个新项目都会生成一个新配置,一个新的,一个新的......

您需要弄清楚可以从每种配置中获得多少种方式。您可以为此使用蛮力,并保存值。当您知道这一点时,您可以将数字放入矩阵中;每一列都是from-configuration,每一行都是to-configuration。

考虑一个计数向量v;其中的每个单元格表示在 n 个步骤中获得某些配置的方法的数量。从初始计数向量开始(全为零,除了一个表示空配置的 1,n = 0)。如果将此向量与矩阵相乘 (v x A),您会将这些计数提前一步 (n = 1)。重复其他步骤。

现在是有趣的部分。如果将此矩阵与自身相乘 (A x A),您将得到一个用于向前移动两代的矩阵。再次 (A2 x A2),您将得到一个移动 4 代的矩阵。只需几次迭代,您就可以使用这种技术将其向前移动数千(或数百万)代。

您可以在 Wikipedia 上阅读有关 exponentiation by squaring 的更多信息。


如果上面的速度太慢,你可以尝试为你找到的值找到一个递归关系。取序列的前几个值,并将它们放入方程组中:

a · x1 + b · x2 = x3
a · x2 + b · x3 = x4

求解 ab。然后生成序列,将最后两个数字乘以 ab 并相加得到下一个。

如果这不能重现序列,您可以增加大小:

a · x1 + b · x2 + c · x3 = x4
a · x2 + b · x3 + c · x4 = x5
a · x3 + b · x4 + c · x5 = x6

求解abc。进一步增加,直到你得到一些有用的东西。如果你再走一步,你应该得到一个未确定的系统。

可能没有递归关系(至少没有线性关系)。在这种情况下,您可以增加尺寸,但您永远找不到任何可行的方法。


举个简单的例子。让我们考虑 K = 1。这将给我们一个大小为 3 (= 2 K + 1) 的邻域,以及 8 个不同的配置 (= 22 K + 1).

要选择一个符号,我将使用 # 表示已占用或无法访问,并使用 . 表示免费。

要生成矩阵,我们必须考虑通过添加单个符号可以将哪些模式转换为哪些模式。

  • 对于以空闲槽开头的模式,我们必须将下一个符号放在最左边。否则我们会在序列中出现间隙。

  • 对于模式###,我们没有任何空闲空间可以放置任何东西,所以这是一条死胡同。

  • 对于剩余的图案,可以在任意空闲位置放置一个符号,然后将图案移动一个空格(移动到下一个位置)。

矩阵可能如下所示:

    ... ..# .#. .## #.. #.# ##. ###
...  1   0   0   0   0   0   0   0
..#  0   0   1   0   0   0   0   0
.#.  0   0   0   0   1   0   0   0
.##  0   0   0   0   0   0   1   0
#..  0   0   1   0   1   0   0   0
#.#  0   0   0   0   0   0   1   0
##.  0   0   0   0   0   0   1   0
###  0   0   0   0   0   0   0   0

作为起始模式,我们将采用#..。这是因为我们不能将第一个符号放在序列开始之前。一个符号的序列,只能用一种方式写,所以起始计数向量变为0 0 0 0 1 0 0 0

我们想要的最终模式是#..。序列末尾没有符号,但必须填写剩余的符号。模式是转移后的。这意味着我们要查看向量中的位置 5(从 1 开始计数)。

我们得到的前几个值是:

n   p(n,1)
1        1
2        2
3        3
4        5
5        8
6       13
7       21
8       34

当然,大部分矩阵都是多余的。您可以花一些时间来消除行和列。我不会演示这个。

一旦我们有了一些值,我们就可以尝试找到递归关系。首先让我们尝试一个大小为 1 的系统:

一个 = 2

这将解决 a = 2。但是如果我们尝试下一个值,我们将看到如果下一个值已经失败:

a = 2·2 = 4 ≠ 3。

接下来,让我们尝试一个大小为 2 的系统:

a + 2·b = 3
a + 3·b = 5

这解决了 a = b = 1。这实际上会生成整个序列。

a + 5·b = 3·1 + 5·1 = 8
a + 8·b = 5·1 + 8·1 = 13
...

如果我们尝试更大的系统,我们会遇到问题:

a + 2·b + 3·c = 5
a + 3·b + 5·c = 8
a + 5·b + 8·c = 13

这解决了 a = 1 - c, b = 2 - c,没有任何值c.

如果我们尝试 K = 2 的序列(使用上述方法生成):1 2 6 14 31 73 172 400 932 2177

这在大小为 5 之前不会给出有效的解决方案:a = -1, b = 0, c = 2, d = 0, e = 2。这与您找到的关系相同。

【讨论】:

  • 优秀的答案!我明天试试这个。有没有一种简单的方法可以找到递归关系?今天早些时候,我正在认真考虑编写一个 C 程序来执行此操作,但我不确定它是否可行。 k=2 的递归关系是 2*x5 + 2*x3 - x1 = x6,以防您在问题中错过了它。与 k=1 相比,它似乎相当复杂,所以我不知道 k=3 会发生什么。
  • 想不通...仍在寻找答案!
  • @Tozar 添加了矩阵生成和递归关系查找的示例。
  • K = 3 的递归关系实际上有 14 个系数。 (只有两个为零。)
  • 有趣的是,尽管您假设存在一个递归公式,但很容易证明一个存在,并确定其顺序的最大界限。首先,矩阵满足其自身的特征多项式(由 Cayley-Hamilton 定理),其阶数为 2^{2K+1},即 sum k_i M^i = 0。现在我们感兴趣的项可以表示为a_n = u^T M^n v 。最后 sum k_i a_i = sum k_i u^T M^i v = u^T ( sum k_i M^i ) v = 0。因此,递归公式中的系数实际上是 M 的特征多项式中的系数(带有一些符号重新排列) .
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-19
  • 2017-07-07
  • 2018-05-16
  • 2017-01-18
  • 2019-03-20
  • 2013-10-28
相关资源
最近更新 更多