web-dev-qa-db-fra.com

Moyen rapide de calculer n! mod m où m est premier?

J'étais curieux de savoir s'il y avait un bon moyen de faire cela. Mon code actuel est quelque chose comme:

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

Mais cela semble assez lent!

Je ne peux pas non plus calculer n! puis appliquez le module premier car parfois n est si grand que n! est tout simplement impossible de calculer explicitement. 

Je suis aussi tombé sur http://en.wikipedia.org/wiki/Stirling%27s_approximation et je me demande si cela peut être utilisé ici d'une manière ou d'une autre

Ou, comment pourrais-je créer une fonction récursive mémoisée en C++?

43
John Smith

Élargir mon commentaire à une réponse:

Oui, il existe des moyens plus efficaces de le faire. Mais ils sont extrêmement en désordre.

Donc, à moins que vous n'ayez vraiment besoin de cette performance supplémentaire, je ne suggère pas d'essayer de les implémenter.


La clé est de noter que le module (qui est essentiellement une division) va être l'opération de goulot d'étranglement. Heureusement, il existe des algorithmes très rapides qui vous permettent de moduler plusieurs fois le même nombre.

Ces méthodes sont rapides car elles éliminent essentiellement le module.


Ces méthodes à elles seules devraient vous donner une accélération modérée. Pour être vraiment efficace, vous devrez peut-être dérouler la boucle pour obtenir un meilleur IPC:

Quelque chose comme ça:

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

mais en prenant en compte pendant un nombre impair d'itérations et en les combinant avec l'une des méthodes que j'ai liées ci-dessus.

Certains pourraient faire valoir que le déroulage de boucle devrait être laissé au compilateur. Je soutiendrai que les compilateurs ne sont actuellement pas assez intelligents pour dérouler cette boucle particulière. Regardez de plus près et vous verrez pourquoi.


Notez que bien que ma réponse soit indépendante de la langue, elle s’adresse principalement au C ou au C++.

16
Mysticial

n peut être arbitrairement grand

Eh bien, n ne peut pas être arbitrairement grand - si n >= m, alors n! ≡ 0 (mod m)(car m est l'un des facteurs, par la définition de factorielle).


En supposant que n << m et que vous ayez besoin d'une valeur exact, votre algorithme ne peut pas aller plus vite, à ma connaissance. Cependant, si n > m/2, vous pouvez utiliser l'identité suivante ( le théorème de Wilson _ - Merci @Daniel Fischer!)

(image)

pour limiter le nombre de multiplications à environ 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) 

Cela nous donne un moyen simple de calculer n! (mod m) en multiplications m-n-1, plus un inverse modulaire :

 def factorialMod (n, module): 
 ans = 1 
 si n <= module // 2: 
 # calcule la factorielle normalement (le bon argument de range () est exclusif) 
 pour i dans la plage (1, n + 1): 
 ans = (ans * i)% module 
 autre:
 #Fancypants méthode for large n 
 pour i dans la plage (n + 1, module): 
 ans = (ans * i)% module 
 ans =  modinv (ans, modulus) 
 ans = -1 * ans + module 
 return et% module 

Nous pouvons reformuler l’équation ci-dessus d’une autre manière, avec des performances légèrement supérieures. En utilisant l'identité suivante:

(image)

on peut reformuler l'équation

 n! - [(n + 1) * ... * (m-2) * (m-1)]-1  (mod m) 
 n! - [(n + 1-m) * ... * (m-2-m) * (m-1-m)]-1  (mod m) 
 (ordre inverse des termes) 
 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) 

Ceci peut être écrit en Python comme suit:

 def factorialMod (n, module): 
 ans = 1 
 si n <= module // 2: 
 # calcule la factorielle normalement (le bon argument de range () est exclusif) 
 pour i dans la plage (1, n + 1): 
 ans = (ans * i)% module 
 autre:
 #Fancypants méthode for large n 
 pour i dans la plage (1, module-n): 
 ans = (ans * i)% module 
 ans =  modinv (ans, modulus) 

 #Depuis m est un nombre premier-impair, (-1) ^ (m-n) = -1 si n est pair, +1 si n est impair 
 si n% 2 == 0: 
 ans = -1 * ans + module 
 return et% module 

Si vous n'avez pas besoin d'une valeur exacte, la vie devient un peu plus facile - vous pouvez utiliser approximation de Stirling pour calculer une valeur approximative en O(log n) time (en utilisant exponentiation par quadrature ).


Enfin, je devrais mentionner que si le temps presse et que vous utilisez Python, essayez de passer à C++. D'après votre expérience personnelle, attendez-vous à une augmentation de la vitesse d'au moins un ordre de grandeur, simplement parce que c'est exactement le type de boucle étroite liée au processeur que le code natif excelle à (également, pour une raison quelconque, GMP semble beaucoup plus finement réglé que Bignum de Python).

n! mod m peut être calculé en O (n1/2 + ε) des opérations à la place du naïf O (n). Cela nécessite l’utilisation de la multiplication polynomiale FFT et n’est utile que pour les très grands n, par exemple. n> 104.

Un aperçu de l'algorithme et quelques timings peuvent être vus ici: http://fredrikj.net/blog/2012/03/factorials-mod-n-and-wilsons-theorem/

11
Fredrik Johansson

Si nous voulons calculer M = a*(a+1) * ... * (b-1) * b (mod p), nous pouvons utiliser l'approche suivante si nous supposons que nous pouvons ajouter, soustraire et multiplier rapidement (mod p), et obtenir une complexité de temps d'exécution de O( sqrt(b-a) * polylog(b-a) ).

Pour simplifier, supposons que (b-a+1) = k^2 est un carré. Désormais, nous pouvons diviser notre produit en k parties, à savoir M = [a*..*(a+k-1)] *...* [(b-k+1)*..*b]. Chacun des facteurs dans ce produit est de la forme p(x)=x*..*(x+k-1), pour x approprié.

En utilisant un algorithme de multiplication rapide de polynômes, tel que Algorithme de Schönhage – Strassen , de manière divisée, on peut trouver les coefficients de la fonction polynomiale p(x) in O( k * polylog(k) ). Apparemment, il existe un algorithme pour substituer des points k dans le même polynôme degré-k dans O( k * polylog(k) ), ce qui signifie que nous pouvons calculer rapidement p(a), p(a+k), ..., p(b-k+1).

Cet algorithme de substitution de nombreux points dans un polynôme est décrit dans le livre "Prime numbers" de C. Pomerance et R. Crandall. Finalement, lorsque vous avez ces valeurs k, vous pouvez les multiplier dans O(k) et obtenir la valeur souhaitée. 

Notez que toutes nos opérations ont été effectuées avec (mod p). Le temps d'exécution exact est O(sqrt(b-a) * log(b-a)^2 * log(log(b-a))).

6
ohad

En développant mon commentaire, cela prend environ 50% du temps pour tout n dans [100, 100007] où 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
1
Andrew Morton

J'ai trouvé cette fonction suivante sur Quora:
Avec f (n, m) = n! mod m;

function f(n,m:int64):int64;
         begin
              if n = 1 then f:= 1
              else f:= ((n mod m)*(f(n-1,m) mod m)) mod m;
         end;

Probablement battre en utilisant une boucle fastidieuse et en multipliant un grand nombre stocké dans une chaîne. En outre, il est applicable à tout nombre entier m.
Le lien où j'ai trouvé cette fonction: https://www.quora.com/How-do-you-calculate-n-mod-m-where-n-is---1-1000s -m-est-un-très-grand-nombre-premier-eg-n-1000-m-10-9 + 7

0
Cong Tran An