n は任意に大きくすることができます
まあ、任意n
に大きくすることはできません- if 、 then (階乗の定義により、要因の1つであるため)。n >= m
n! ≡ 0 (mod m)
m
私の知る限り、正確なn << m
値が必要であると仮定すると、アルゴリズムはこれ以上速くなりません。ただし、 の場合、次の恒等式を使用できます(ウィルソンの定理- @Daniel Fischer に感謝します!)n > m/2

乗算の数を約に制限するm-n
(m-1)! ≡ -1 (mod m)
1 * 2 * 3 * ... * (n-1) * n * (n+1) * ... * (m-2) * (m-1) ≡ -1 (mod m)
ん!* (n+1) * ... * (m-2) * (m-1) ≡ -1 (mod m)
ん!≡ -[(n+1) * ... * (m-2) * (m-1)] -1 (mod m)
n! (mod m)
これにより、m-n-1
乗算で計算する簡単な方法とモジュラー逆数が得られます。
def factorialMod(n, モジュラス):
ans=1
n <=係数//2の場合:
#階乗を普通に計算する(range()の右引数は排他)
i の範囲 (1,n+1):
ans = (ans * i) % モジュラス
そうしないと:
#n が大きい場合の Fancypants メソッド
for i の範囲 (n+1,係数):
ans = (ans * i) % モジュラス
ans = modinv(ans, モジュラス)
ans = -1*ans + モジュラス
リターン ans % モジュラス
上記の方程式を別の方法で言い換えることができます。次の ID を使用します。

方程式を次のように言い換えることができます
ん!≡ -[(n+1) * ... * (m-2) * (m-1)] -1 (mod m)
ん!≡ -[(n+1-m) * ... * (m-2-m) * (m-1-m)] -1 (mod m)
(用語の逆順)
ん!≡ -[(-1) * (-2) * ... * -(mn-2) * -(mn-1)] -1 (mod m)
ん!≡ -[(1) * (2) * ... * (mn-2) * (mn-1) * (-1) (mn-1) ] -1 (mod m)
ん!≡ [(mn-1)!] -1 * (-1) (mn) (mod m)
これは Python で次のように記述できます。
def factorialMod(n, モジュラス):
ans=1
n <=係数//2の場合:
#階乗を普通に計算する(range()の右引数は排他)
i の範囲 (1,n+1):
ans = (ans * i) % モジュラス
そうしないと:
#n が大きい場合の Fancypants メソッド
for i in range(1,modulus-n):
ans = (ans * i) % モジュラス
ans = modinv(ans, モジュラス)
#m は奇素数なので (-1)^(mn) = n が偶数なら -1、n が奇数なら +1
n % 2 == 0 の場合:
ans = -1*ans + モジュラス
リターン ans % モジュラス
正確な値が必要ない場合は、少し楽になります。スターリングの近似O(log n)
を使用して、時間の近似値を計算できます( 2 乗によるべき乗を使用)。
最後に、これが時間が重要で、Python を使用している場合は、C++ に切り替えてみてください。個人的な経験からすると、1 桁以上の速度の向上を期待する必要があります。これは、ネイティブにコンパイルされたコードが優れているまさに一種の CPU バウンドのタイトループであるためです (また、何らかの理由で、GMP はPython の Bignum よりもはるかに細かく調整されています)。