排列顺序的方法数量

时间:2018-04-20 14:52:14

标签: algorithm permutation

我有一个M字符,从这些字符开始我需要制作一个长度为N的序列,这样两个连续的字符就不会相同,并且序列的第一个和最后一个字符也是固定的。所以我需要找到总的方式。

我的方法:

动态编程。 如果第一个和最后一个字符是' 0'和' 1'

dp[1][0]=1 , dp[1][1]=1

for(int i=2;i<N;i++)
    for(int j=0;j<M;j++)
         for(int k=0;k<M;k++)
                  if(j!=k) dp[i][j]+=dp[i-1][k]

所以最终答案是summation dp[n-1][i] , i!=1

问题:

此处N长度在10 ^ 15左右过大而M128左右,如何在不使用数组的情况下找到排列数?

2 个答案:

答案 0 :(得分:2)

假设M已修复。设D(n)为长度n的序列数,没有重复的字符,其中第一个和最后一个字符不同(但是是固定的)。设S(n)为长度n的序列数,其中第一个和最后一个字符相同(但是固定)。

例如,D(6)a????b形式的字符串数(对于某些ab - 注意到计算它并不重要我们选择的两个字符,以及?代表其他字符的位置)。同样,S(6)a????a形式的字符串数。

考虑n>3形式的长度为a....?b的序列。 ?可以是m-1个字符中的任何一个(b除外)。其中之一是a。所以D(n) = S(n-1) + (m-2)D(n-1)。使用类似的参数,可以找出S(n) = (M-1)D(n-1)

例如,a??b形式有多少个字符串?好吧,b之前的字符可能是a或其他内容。它是a时有多少个字符串?嗯,它与a?a形式的字符串数相同。当它是别的东西时,有多少个字符串?那么它与a?c形式的字符串数量乘以c选择的数量相同(即:m-2 - 除了a之外的所有内容我们已经计算了,b被规则排除了。

如果n是奇数,我们可以考虑中间字符。考虑n形式的长度为a...?...b的序列。 ?(位于字符串的中心)可以是ab或其他M-2个字符之一。因此D(2n+1) = S(n+1)D(n+1) + D(n+1)S(n+1) + (M-2)D(n+1)D(n+1)。同样,S(2n+1) = S(n+1)S(n+1) + (M-1)D(n+1)D(n+1)

适用于小型nS(2)=0S(3)=M-1D(2)=1D(3)=M-2

我们可以使用上面的等式(偶数n>3的第一组,奇数n>3的第二组,以及n=23的基本情况你需要在O(log N)算术运算中得到的结果。大概这个问题要求你以模数的形式计算结果(因为结果像O(M^(N-2))一样增长,但这很容易融入到结果

使用此方法的工作代码:

def C(n, m, p):
    if n == 2:
        return 0, 1
    if n == 3:
        return (m-1)%p, (m-2)%p
    if n % 2 == 0:
        S, D = C(n-1, m, p)
        return ((m-1) * D)%p, (S + (m-2) * D)%p
    else:
        S, D = C((n-1)//2+1, m, p)
        return (S*S + (m-1)*D*D)%p, (2*S*D + (m-2)*D*D)%p

请注意,在此代码中,C(n,m,p)返回两个数字 - S(n)%p和D(n)%p。

例如:

>>> p = 2**64 - 59  # Some large prime
>>> print(C(4, 128, p))
>>> print(C(5, 128, p))
>>> print(C(10**15, 128, p))

(16002, 16003)
(2032381, 2032380)
(12557489471374801501, 12557489471374801502)

看一下这些例子,好像是D(n) = S(n) + (-1)^n。如果这是真的,我猜想代码可以简化一下。

另一种更有效的方法是使用矩阵和第一组方程。 (抱歉,ascii art - 此图是vector = matrix * vector):

(D(n)) = (M-2  1) * (D(n-1))
(S(n)) = (M-1  0)   (S(n-1))

伸缩这个,并使用D(2)= 1,S(2)= 0:

(D(n)) = (M-2  1)^(n-2) (1)
(S(n)) = (M-1  0)       (0)

您可以通过在O(log n)时间内平方来使用取幂来执行矩阵幂。

这是工作代码,包括示例(您可以检查产生与上面代码相​​同的值)。大多数代码实际上是矩阵乘法和矩阵幂 - 如果你使用那个包,你可以用numpy代码替换很多代码。

def mat_mul(M, N, p):
    R = [[0, 0], [0, 0]]
    for i in range(2):
        for j in range(2):
            for k in range(2):
                R[i][j] += M[i][k] * N[k][j]
                R[i][j] %= p
    return R

def mat_pow(M, n, p):
    if n == 0:
        return [[1, 0], [0, 1]]
    if n == 1:
        return M
    if n % 2 == 0:
        R = mat_pow(M, n//2, p)
        return mat_mul(R, R, p)
    return mat_mul(M, mat_pow(M, n-1, p), p)

def Cmat(n, m, p):
    M = [((m-2), 1), (m-1, 0)]
    M = mat_pow(M, n-2, p)
    return M[1][0], M[0][0]

p = 2**64 - 59
print(Cmat(4, 128, p))
print(Cmat(5, 128, p))
print(Cmat(10**15, 128, p))

答案 1 :(得分:1)

您只需要计算可接受序列的数量,而不是明确找到它们。事实证明,大多数角色并不重要。重要的只有4种字符:

  • 第一个字符
  • 最后一个字符
  • 最后使用的字符,因此您不会连续重复字符
  • 所有其他角色

换句话说,您不需要迭代所有10^15个字符。你只需要考虑上面的四种情况,因为大多数字符都可以归结为最后一种情况。