web-dev-qa-db-ja.com

nをすばやく計算する方法! mod mここで、mは素数ですか?

これを行う良い方法があるかどうか興味がありました。私の現在のコードは次のようなものです:

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++で再帰的なメモ関数を作成するにはどうすればよいですか?

43
John Smith

コメントを回答に拡大:

はい、これを行うより効率的な方法があります。 しかし、それらは非常に乱雑です

そのため、その余分なパフォーマンスが本当に必要でない限り、これらを実装することはお勧めしません。


重要なのは、モジュラス(本質的に除算)がボトルネック操作になることに注意することです。幸いなことに、同じ数で何度もモジュラスを実行できる非常に高速なアルゴリズムがいくつかあります。

これらの方法は、本質的に弾性率を排除するため、高速です。


これらの方法だけで、中程度のスピードアップが得られます。本当に効率的にするには、ループを展開して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

しかし、奇数回の反復を考慮し、上記でリンクした方法の1つと組み合わせます。

ループ展開はコンパイラーに任せるべきだと主張する人もいるかもしれません。コンパイラーは現在、この特定のループを展開するほどスマートではないと反論します。よく見ると、その理由がわかります。


私の答えは言語に依存しませんが、主にCまたはC++を対象としています。

16
Mysticial

nは任意に大きくすることができます

さて、n任意に大きくすることはできません-_n >= m_の場合、n! ≡ 0 (mod m)(階乗の定義により、mは要因の1つであるため)


_n << m_があり、exact値が必要であると仮定すると、私の知る限り、アルゴリズムを高速化することはできません。ただし、_n > m/2_の場合、次のIDを使用できますウィルソンの定理 -@Daniel Fischerに感謝!)

(image)

乗算の数を約_m-n_に制限する

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

これにより、_m-n-1_乗算でn! (mod m)を計算する簡単な方法に加えて、 モジュラー逆行列 が得られます。

 def factorialMod(n、モジュール):
 ans = 1 
 if n <=モジュール//2:
#階乗を正常に計算します(範囲の右引数) ()は排他的)
 for i in range(1、n + 1):
 ans =(ans * i)%モジュラス
 else:
 #Fancypants range(n + 1、modulus)のiの大きなn 
の方法:
 ans =(ans * i)%モジュラス
 ans =  modinv(ans、モジュラス)  
 ans = -1 * ans +モジュラス
 ans%モジュラス
を返します

上記の式を別の方法で言い換えることができます。これは、わずかに高速に実行される場合とされない場合があります。次のIDを使用します。

(image)

方程式を次のように言い換えることができます

 n! ≡-[(n + 1)* ... *(m-2)*(m-1)]-1 (mod m)
 n! ≡-[(n + 1-m)* ... *(m-2-m)*(m-1-m)]-1 (mod m)
(用語の逆順)
 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、モジュール):
 ans = 1 
 if n <=モジュール//2:
#階乗を正常に計算します(範囲の右引数) ()は排他的)
 for i in range(1、n + 1):
 ans =(ans * i)%モジュラス
 else:
 #Fancypants range(1、modulus-n)のiに対する大きなn 
の方法:
 ans =(ans * i)%モジュラス
 ans = ---(modinv(ans、モジュラス)  
 
 #mは奇数の素数であるため、nが偶数の場合は(-1)^(mn)= -1、nが奇数の場合は+1 
 if n%2 == 0:
 ans = -1 * ans +モジュラス
 ans%モジュラス
を返します

exact値が必要ない場合、寿命が少し楽になります- スターリングの近似 を使用して近似値を計算できますO(log n) timeの値二乗によるべき乗を使用します。


最後に、これがタイムクリティカルでPythonを使用している場合は、C++に切り替えてみてください。個人的な経験から、速度の大幅な増加以上を期待する必要があります。これは、これがまさにネイティブにコンパイルされたコードexcelsat(また、何らかの理由で、GMPはPythonのBignumよりもはるかに細かく調整されているようです)

n! mod mはO(n1/2 +ε)単純なO(n)の代わりの操作。これにはFFT多項式乗算の使用が必要であり、非常に大きなn、例えばn> 104

アルゴリズムといくつかのタイミングの概要は、ここで見ることができます: http://fredrikj.net/blog/2012/03/factorials-mod-n-and-wilsons-theorem/

11

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]に分割できます。この製品の各要因は、適切なxp(x)=x*..*(x+k-1)という形式です。

Schönhage–Strassenアルゴリズム などの多項式の高速乗算アルゴリズムを除算と征服の方法で使用することにより、多項式p(x) in O( k * polylog(k) )の係数を見つけることができます。さて、明らかにO( k * polylog(k) )の同じ次数k多項式のk点を置換するアルゴリズムがあります。つまり、p(a), p(a+k), ..., p(b-k+1)を高速に計算できます。

多くの点を1つの多項式に置き換えるこのアルゴリズムは、C。PomeranceとR. Crandallの著書「Prime numbers」に記載されています。最終的に、これらのk値がある場合、O(k)でそれらを乗算し、目的の値を取得できます。

_(mod p)_を使用したすべての操作に注意してください。正確な実行時間はO(sqrt(b-a) * log(b-a)^2 * log(log(b-a)))です。

6
ohad

私のコメントを拡大すると、これは[100、100007]のすべてのnに対して約50%の時間がかかります。ここで、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

私はこの次の関数をquoraで見つけました:
With 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;

おそらく時間のかかるループを使用して、文字列に格納されている大きな数を掛けてビートを打ちます。また、任意の整数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

0
Cong Tran An