基本的にはここで書いたModの割り算に関する言及の書き直しみたいなもんです。
【競プロ】個人的に優先度の高いライブラリ - あめんばーどのバーチャル日記
背景
Modの割り算にはその数の逆元を求める必要があり、それを求める方法としてフェルマーの小定理と、拡張ユークリッド互除法がある。
フェルマーの小定理
が素数なら なのでは逆元である。これは繰り返し二乗法を使えば
拡張ユークリッド互除法
今回言及する内容なので後述
二つの速度的な違い
どちらもである。ただし例えばの場合、フェルマーの小定理は常に30回。対して拡張ユークリッド互除法は1回~36回(平均は17.932回)
これはフェルマーの小定理はの底がに対し、ユークリッド互除法は最悪ケースがフィボナッチ数列の為なためである。
実際拡張ユークリッド互除法の最悪ケースのひとつの逆元を回分求めると、この通り拡張ユークリッド互除法のほうが遅い
拡張ユークリッドの互除法:4121ms
フェルマーの小定理:3129ms
逆にでやるとぶっちぎりで早い。
拡張ユークリッドの互除法:276ms
フェルマーの小定理:3185ms
期待値で考えるとやはりユークリッド互除法のほうが優れていそうである。とはいえ自分の中でも拡張ユークリッド互除法が曖昧だったのでいろいろ証明したりしたのでまとめた。って感じ
拡張ユークリッド互除法
コード
tuple<ll, ll, ll> extGcd(ll a, ll b) { if (b == 0) return make_tuple(a, 1, 0); // 行きがけのゴール地点 auto [g, x, y] = extGcd(b, a % b); return make_tuple(g, y, x - y * (a / b)); // 帰りがけの動作 }
実装自体はこの4行で十分なのである。tupleはそれぞれGCDと下記を満たすx,yを返している。
何してるのか
3行目はユークリッド互除法。 4行目は式を整理すれば返した値が成立していることがわかる
に対して
と、うまい感じに成り立ってくれる。実際の数字で試しているのは前のブログ参照。
1行目と2行目はGCDが求まった瞬間にはgcdなので、下記が絶対成り立ってくれる。
bの係数は0
そこで以前疑問に持たれたのが、の係数はである必要性である。もちろんとなるは無数にあるので、にしなくても別の値が出てきてくれるだけなのだ。
だが0にすべきである。疑問に持たれた時や前回のブログでは「値が膨らまなさそう」と曖昧に言った。厳密には下記が成り立つからである
初期のの係数をにした場合、必ず以後が成立する
証明はシンプル。最初こそは成立しないが、その寸前の状態はその条件が成立しているはずである。
ここでが成立しているのを仮定として機能的に示すために先程取り出したを利用してこの式に戻ってこよう
これでが成立していればいい。前者は仮定である。後者は三角不等式により
よって係数をにすれば先程の条件は常に成り立つわけである。つまりオーバーフローしない。とてもうれしい。