n может быть сколь угодно большим
Ну, n
не может быть произвольно большим - если n >= m
, то n! ≡ 0 (mod m)
(потому что m
является одним из факторов по определению факториала) .
Предполагая, что n << m
и вам нужно точное значение, ваш алгоритм не может работать быстрее, насколько мне известно. Однако, если n > m/2
, вы можете использовать следующее тождество ( Теорема Вильсона - Спасибо @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)]<sup>-1</sup> (mod m)
Это дает нам простой способ вычисления n! (mod m)
в m-n-1
умножениях плюс модульное обратное :
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 = <a href="http://www.algorithmist.com/index.php/Modular_inverse" rel="noreferrer">modinv(ans, modulus)</a>
ans = -1*ans + modulus
return ans % modulus
Мы можем перефразировать вышеприведенное уравнение другим способом, который может или не может работать немного быстрее. Используя следующий идентификатор:
мы можем перефразировать уравнение как
n! ≡ -[(n+1) * ... * (m-2) * (m-1)]<sup>-1</sup> (mod m)
n! ≡ -[(n+1-m) * ... * (m-2-m) * (m-1-m)]<sup>-1</sup> (mod m)
(reverse order of terms)
n! ≡ -[(-1) * (-2) * ... * -(m-n-2) * -(m-n-1)]<sup>-1</sup> (mod m)
n! ≡ -[(1) * (2) * ... * (m-n-2) * (m-n-1) * (-1)<sup>(m-n-1)</sup>]<sup>-1</sup> (mod m)
n! ≡ [(m-n-1)!]<sup>-1</sup> * (-1)<sup>(m-n)</sup> (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 = <a href="http://www.algorithmist.com/index.php/Modular_inverse" rel="noreferrer">modinv(ans, modulus)</a>
#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
Если вам не нужно точное значение, жизнь становится немного проще - вы можете использовать приближение Стирлинга , чтобы вычислить приблизительное значение за O(log n)
время ( используя возведение в степень путем возведения в квадрат ) .
Наконец, я должен отметить, что если это критично ко времени, и вы используете Python, попробуйте переключиться на C ++. Исходя из личного опыта, следует ожидать увеличения скорости на порядок или более, просто потому, что это именно тот тип замкнутого цикла с привязкой к процессору, который изначально скомпилированный код превосходит при (также, по любой причине, GMP кажется намного более точно настроенным, чем Bignum Python) .