快速计算n的方法! mod m其中m是素数?

时间:2012-03-15 20:50:32

标签: algorithm math factorial modulus

我很好奇是否有一个很好的方法来做到这一点。我目前的代码如下:

def factorialMod(n, modulus):
    ans=1
    for i in range(1,n+1):
        ans = ans * i % modulus    
    return ans % modulus

但似乎很慢!

我也无法计算n!然后应用素数模数,因为有时n是如此之大,以至于n!明确计算是不可行的。

我也遇到了http://en.wikipedia.org/wiki/Stirling%27s_approximation并想知道这是否可以在某种程度上使用?

或者,我如何在C ++中创建一个递归的,memoized函数?

8 个答案:

答案 0 :(得分:39)

  

n可以任意大

好吧,n不能任意大 - 如果是n >= m,那么n! ≡ 0 (mod m) (因为m是其中之一这些因素,通过阶乘的定义)


假设n << m并且您需要完全值,据我所知,您的算法无法更快地获得。但是,如果n > m/2,您可以使用以下身份Wilson's theorem - 谢谢@Daniel Fischer!)

(image)

将乘法次数限制为大约m-n

(m-1)! ≡ -1 (mod m)
1 * 2 * 3 * ... * (n-1) * n * (n+1) * ... * (m-2) * (m-1) ≡ -1 (mod m)
n! * (n+1) * ... * (m-2) * (m-1) ≡ -1 (mod m)
n! ≡ -[(n+1) * ... * (m-2) * (m-1)]-1 (mod m)

这为我们提供了一种简单的方法来计算n! (mod m)乘法中的m-n-1加上modular inverse

def factorialMod(n, modulus):
    ans=1
    if n <= modulus//2:
        #calculate the factorial normally (right argument of range() is exclusive)
        for i in range(1,n+1):
            ans = (ans * i) % modulus   
    else:
        #Fancypants method for large n
        for i in range(n+1,modulus):
            ans = (ans * i) % modulus
        ans = modinv(ans, modulus)
        ans = -1*ans + modulus
    return ans % modulus

我们可以用另一种方式重新表述上述等式,这可能会或可能不会稍微快一些。使用以下标识:

(image)

我们可以将等式重新定义为

n! ≡ -[(n+1) * ... * (m-2) * (m-1)]-1 (mod m)
n! ≡ -[(n+1-m) * ... * (m-2-m) * (m-1-m)]-1 (mod m)
       (reverse order of terms)
n! ≡ -[(-1) * (-2) * ... * -(m-n-2) * -(m-n-1)]-1 (mod m)
n! ≡ -[(1) * (2) * ... * (m-n-2) * (m-n-1) * (-1)(m-n-1)]-1 (mod m)
n! ≡ [(m-n-1)!]-1 * (-1)(m-n) (mod m)

这可以用Python编写如下:

def factorialMod(n, modulus):
    ans=1
    if n <= modulus//2:
        #calculate the factorial normally (right argument of range() is exclusive)
        for i in range(1,n+1):
            ans = (ans * i) % modulus   
    else:
        #Fancypants method for large n
        for i in range(1,modulus-n):
            ans = (ans * i) % modulus
        ans = modinv(ans, modulus)

        #Since m is an odd-prime, (-1)^(m-n) = -1 if n is even, +1 if n is odd
        if n % 2 == 0:
            ans = -1*ans + modulus
    return ans % modulus

如果您不需要完全值,生活会变得更轻松 - 您可以使用Stirling's approximation计算O(log n)时间中的近似值(使用exponentiation by squaring


最后,我应该提一下,如果这是时间关键的并且您正在使用Python,请尝试切换到C ++。从个人经验来看,你应该期望速度增加一个数量级或更多,仅仅是因为这正是那种以CPU为单位的紧密循环,本机编译的代码 excels (无论出于何种原因,GMP似乎比Python的Bignum更精细)

答案 1 :(得分:16)

将我的评论扩展到答案:

是的,有更有效的方法可以做到这一点。 但它们非常混乱。

因此,除非你真的需要额外的表现,否则我不建议尝试实施这些。


关键是要注意模数(基本上是一个除法)将成为瓶颈操作。幸运的是,有一些非常快速的算法允许您多次执行相同数量的模数。

这些方法很快,因为它们基本上消除了模量。


单独使用这些方法可以为您提供适度的加速。为了真正有效,您可能需要展开循环以允许更好的IPC:

这样的事情:

ans0 = 1
ans1 = 1
for i in range(1,(n+1) / 2):
    ans0 = ans0 * (2*i + 0) % modulus    
    ans1 = ans1 * (2*i + 1) % modulus    

return ans0 * ans1 % modulus

但考虑到奇数#次迭代并将其与我上面链接的方法之一相结合。

有些人可能认为循环展开应留给编译器。我将反驳说,编译器目前还不够聪明,无法展开这个特定的循环。仔细看看你会明白为什么。


请注意,尽管我的答案与语言无关,但它主要用于C或C ++。

答案 2 :(得分:11)

N! mod m可以用O(n 1/2 +ε)运算来计算,而不是天真的O(n)。这需要使用FFT多项式乘法,并且仅适用于非常大的n,例如, n> 10 4

此处可以看到算法概要和一些时间:http://fredrikj.net/blog/2012/03/factorials-mod-n-and-wilsons-theorem/

答案 3 :(得分:6)

如果我们想要计算M = a*(a+1) * ... * (b-1) * b (mod p),我们可以使用以下方法,如果我们假设我们可以添加,减去和乘以快(mod p),并获得O( sqrt(b-a) * polylog(b-a) )的运行时复杂度。

为简单起见,假设(b-a+1) = k^2是正方形。现在,我们可以将产品划分为k个部分,即M = [a*..*(a+k-1)] *...* [(b-k+1)*..*b]。对于适当的p(x)=x*..*(x+k-1),此产品中的每个因素都采用x形式。

通过使用多项式的快速乘法算法,例如Schönhage–Strassen algorithm,在除法和&amp;征服方式,人们可以找到多项式p(x) in O( k * polylog(k) )的系数。现在,显然有一种算法可以替换k中相同度k基多项式中的O( k * polylog(k) )个点,这意味着我们可以快速计算p(a), p(a+k), ..., p(b-k+1)

这个将多个点代入一个多项式的算法在书中描述了&#34;素数&#34;由C. Pomerance和R. Crandall撰写。最后,当您拥有这些k值时,可以将它们乘以O(k)并获得所需的值。

请注意我们执行的所有操作(mod p)。 确切的运行时间为O(sqrt(b-a) * log(b-a)^2 * log(log(b-a)))

答案 4 :(得分:1)

扩展我的评论,这需要大约50%的时间用于[100,100007]中的所有n,其中m =(117 | 1117):

Function facmod(n As Integer, m As Integer) As Integer
    Dim f As Integer = 1
    For i As Integer = 2 To n
        f = f * i
        If f > m Then
            f = f Mod m
        End If
    Next
    Return f
End Function

答案 5 :(得分:0)

我在quora上发现了以下功能:
用f(n,m)= n! mod m;

var x = $(".imglist[src$=""+splitimgurl]");

可能使用耗时的循环并乘以存储在字符串中的大数字。此外,它适用于任何整数m 我找到此功能的链接:https://www.quora.com/How-do-you-calculate-n-mod-m-where-n-is-in-the-1000s-and-m-is-a-very-large-prime-number-eg-n-1000-m-10-9+7

答案 6 :(得分:-1)

如果n =(m - 1)表示素数m,那么http://en.wikipedia.org/wiki/Wilson's_theorem n! mod m =(m - 1)

也正如已经指出的那样!如果n> mod,则mod m = 0;米

答案 7 :(得分:-11)

假设您选择的平台的“mod”运算符足够快,那么主要取决于您可以计算n!的速度以及可用于计算它的空间。

然后它基本上是一个两步操作:

  1. 计算n! (有很多快速算法,所以我不再重复了)
  2. 取结果的模型
  3. 没有必要复杂化事物,特别是如果速度是关键因素。通常,尽可能少地在循环内执行操作。

    如果您需要反复计算n! mod m,那么您可能想要记住从执行计算的函数中输出的值。与往常一样,这是经典的空间/时间权衡,但查找表非常快。

    最后,您可以将memoization与递归(以及蹦床,如果需要)相结合,以便真正快速。