フェルマーの小定理を使った組み合わせの数とその余りの効率的な求め方

AtCoderのABC042Dで使う、組み合わせの数\({}_n C_r\)とその余りの効率的な求め方についてまとめました。合同式やフェルマーの小定理について書いています。

スポンサーリンク

問題設定

\({}_n C_r = \frac{n!}{(n-r)! r!}\) を求める時には、その数がとても大きくなることが多いため、\( 10^9 + 7\)で割ったあまりの数を使います。

\({}_n C_r\)の計算ではそのまま階乗を使った計算をすると、実行時間が間に合わなくなるため、あらかじめ\(0 \leq x \leq n\)に対して、\(x! (mod 10^9 + 7)\)と\((x!)^{-1} (mod 10^9 + 7)\)を計算してテーブルにしておくことで、その後の\({}_n C_r\) の計算に時間をかけないようにします。

では、その\(x! (mod 10^9 + 7)\)と\((x!)^{-1} (mod 10^9 + 7)\)をどのように計算していくか解説していきます。

\(x! (mod 10^9 + 7)\)のテーブル作成

\(0 \leq x \leq n\)に対して\(x! (mod 10^9 + 7)\)のテーブル作成には、合同式の性質を利用します。

合同式では

$$ a \equiv c, b \equiv d (mod p)$$

ならば、

$$ ab \equiv cd (mod p)$$

が成立します。

よって、

\( 0! \equiv 1 (mod 10^9 +7)\)の両辺に1をかけて、 \( 1! \equiv 1 \times 1 \equiv 1 (mod 10^9 +7)\)を求め、それの両辺に2をかけて\( 2! \equiv 1 \times 2 \equiv 2 (mod 10^9 +7)\)を求め、というのを繰り返して\(x! (mod 10^9 + 7)\)のテーブル作成を行います。

Python3でのコードの例は下の通りです。

MOD = 10**9 + 7
table = [1]
for i in range(1, N+1):
    table.append((table[-1]*i) % MOD)

\(x\)の逆元\(x^{-1}\)とは?

\( x ^{-1} (mod 10^9 + 7)\)とは何か。

それは、\( x x^{-1} = 1\)より、\(x (mod10^9 + 7)\)にかけて\(mod 10^9 + 7\)で1となる数です。\(x^{-1}\)は\( x \equiv 0 (mod 10^9 +7)\)でない任意の\(x\)に対して存在します。

その\(x^{-1}\)を求めるために、フェルマーの小定理

” \(p\)が素数で、整数\(a\)が\(p\)と互いに素であるとき、\(a^{p-1} \equiv 1 (mod p)\) “

を利用します。

\(10^9 + 7\)は素数なので、

$$a^{10^9 + 7 – 1} = a^{10^9 + 6} = a a^{10^9 + 5}\equiv 1 (mod 10^9+7) $$

より、\(a^{-1} = a^{10^9 + 5}\)が導けます。

以上より、\(0 \leq x \leq n\)に対して\((x!)^{-1}\)を求める時には、\((n!)^{-1} (mod 10^9 + 7)\)を求めて、それに\(n\)をかけると\(((n-1)!)^{-1} (mod 10^9 + 7)\)が求められ、それに\(n-1\)をかけると\(((n-2)!)^{-1} (mod 10^9 + 7)\)が求まるというように、高速に逆元のテーブル作成ができます。

Python3でのコードの例は下の通りです。途中までは上のソースコードと同じです。

MOD = 10**9 + 7
table = [1]
for i in range(1, N+1):
    table.append((table[-1]*i) % MOD)

inverse_table = []
inverse_table.append(pow(table[-1], MOD-2, MOD))  % N!(mod MOD) のMOD-2乗のMODでの余り
for i in range(N):
    inverse_table.insert(0, (inverse_table[0]*(N-i)) % MOD)
inverse_table.insert(0, 1)

また、簡単な書き方として\(a^{-1} = a^{10^9 + 5}\)をそれぞれの\(a\)で適応する以下のようなコードも考えられます。

MOD = 10**9 + 7
table = [1]
inverse_table = [1]
for i in range(1, N+1):
    table.append((table[-1]*i) % MOD)
    inverse_table.append(pow(table[-1], MOD-2, MOD))