Python で mod の下での逆元を計算するテーブルを作成する
wakabame.hatenablog.com
では, の素数 で割った余りを求める方法について解説しました.
実は, この計算アルゴリズムは高速化できます.
ボトルネックはどこか?
の計算結果が何度も呼び出される場合を考えましょう. そのたびに
- 階乗の計算が必要になる
- 逆元を求めるために 乗の繰り返し二乗法が必要になる
点がボトルネックです.
前者は予め必要な大きさのリストを構築して, 計算結果を最初に持っておけば で済みます.
一方で, 後者のボトルネックについては, 繰り返し二乗法を全ての整数に対して行わなければならないのが(定数倍とはいえ)しんどいです.
そこで, 次のブログを参考に逆元テーブルの構築を考えます.
drken1215.hatenablog.com
takapt0226.hatenablog.com
以後, を十分大きな素数とします.
逆元テーブルを構築するスクリプト
P = 10**9 + 7 N = 10000 inv_t = [0]+[1] for i in range(2,N): inv_t += [inv_t[P % i] * (P - int(P / i)) % P]
これにより, に対して inv_t[i] は" の法 の下での逆元"を表します.
説明
に対して, の法 の下での逆元 を求めましょう.
まず,
が の法 の下での逆元であるとは,
を満たすこと
ある が存在して,
と定式化できます.
そのような は が の倍数でない限り存在し一意であり, 以後これを と表します.
さて, アルゴリズムを説明していきます.
まず最初に, に対する逆元はそれぞれ と定めてしまいます.
以後, は 以上とし, 未満の逆元は既に得られたとしましょう.
を で割り, その商, 余りをそれぞれ , とおきます. すなわち
が成り立ちます.このとき, は 未満であることに注意します.
両辺に をかけると,
であり, の逆元の定義から,
が成り立ちます.
移項して整理すると,
です.
さらに をかけると,
が成り立ちます.
両辺, で割った余りに着目すると,
を得ます.
は既に得られているので, この方法で小さい数から逐次的に逆数を求めればよく, トータルの計算量は であることがわかりました.