我很好奇是否有一个很好的方法来做到这一点。我目前的代码如下:
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函数?
答案 0 :(得分:39)
n可以任意大
好吧,n
不能任意大 - 如果是n >= m
,那么n! ≡ 0 (mod m)
(因为m
是其中之一这些因素,通过阶乘的定义)。
假设n << m
并且您需要完全值,据我所知,您的算法无法更快地获得。但是,如果n > m/2
,您可以使用以下身份(Wilson's theorem - 谢谢@Daniel Fischer!)
将乘法次数限制为大约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
我们可以用另一种方式重新表述上述等式,这可能会或可能不会稍微快一些。使用以下标识:
我们可以将等式重新定义为
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!
的速度以及可用于计算它的空间。
然后它基本上是一个两步操作:
没有必要复杂化事物,特别是如果速度是关键因素。通常,尽可能少地在循环内执行操作。
如果您需要反复计算n! mod m
,那么您可能想要记住从执行计算的函数中输出的值。与往常一样,这是经典的空间/时间权衡,但查找表非常快。
最后,您可以将memoization与递归(以及蹦床,如果需要)相结合,以便真正快速。