ABC 186 E - Throne
一次合同式についての教育的な良問だったので、考えたことを書き残しておく。
問題
考えたこと
この問題は言い換えると、初期値がsの状態からkを何度か足すことで、nの倍数を作れるか、という問題になる。
つまり、(kx+s)%n==0となる最小のxを求める問題(解が存在しない場合も有り得る)であり、この式を更に変形すると、kx+s≡0 (mod n) => k*x≡-s (mod n) という一次合同式を解く問題に帰着できる。
一次合同式 a*x≡b (mod m) の解を求める手順は以下の通り。
- 整数 a, b, mの最大公約数gを求めて、gでそれぞれの値を割った値に置き換える
- これはただの前処理で、gで割る前と後の合同式は同値
- 次に、a, mが互いに素であるかを確認するためにa, mの最大公約数を求める
- このとき、その最大公約数が1でないなら、解は存在しない(これは乗法逆元が定まらないため)
- 解が存在するなら、mod mにおけるaの乗法逆元a'とすると、その解は、a'*bとなる
注意点
ここに丁寧に記されている通り、乗法逆元の存在する条件は、aとmが互いに素であること。
以下の2点は正であるのに誤解しがちなところので注意する。
上記を踏まえると逆元が存在する場合には、与えられるa, b, mによってはaとmが互いに素ではあるが、mは素数ではないという場合が有り得る。
さらに整理すると、合成数である可能性もあるMが与えられ解として計算結果のmod Mを求めよという問題で計算結果がWAとなるのは、計算過程に除法があるならばmod Mの乗法逆元を求める必要があり、割る数とMが互いに素でない場合(フェルマーの小定理等では)その逆元を正しく求められないからである。
この問題もその一例で、(m-2)乗で逆元が求まらない場合がある。結局この場合の求め方がわからなかったので、以下の解答コードではACLのmodint
を使いお茶を濁している。(誰か教えてください)
解答コード
#include <bits/stdc++.h> using namespace std; #include <atcoder/all> using namespace atcoder; int solve(int a, int b, int m) { int g = gcd(gcd(a, b), m); a /= g, b /= g, m /= g; if (gcd(a, m) != 1) return -1; modint::set_mod(m); modint ma = a; modint mb = b; return (mb * ma.inv()).val(); } signed main() { int t; cin >> t; while (t--) { int n, s, k; cin >> n >> s >> k; cout << solve(k, -s, n) << endl; } }
個人的なメモ
gcd
と__gcd
の挙動は異なる
※この問題で__gcd
を使って実装するとバグります。(__gcd
はパラメータに負数を含む場合に結果が負数になる可能性があるため)
__gcd() と gcd(), 実質一緒だと思ったら負数に対する結果が違うらしく嵌まりかけた
— si💊 (@iiljj) 2020年5月2日
実験の結果,戻り値は
・__gcd() → 0でなく絶対値の小さい引数と同符号
・gcd() → 常に非負整数
どうしてこうなった
__gcd は rotate の実装のために置かれてる内部用の関数で、我々が使うことを意図して置かれてるものではないので
— えびちゃん (@rsk0315_h4x) 2020年5月2日
結論gcd
を使う。
- ACL modintを呼ぶ前に
modint::set_mod
を呼ぶのを忘れない
参考
合成数の可能性があるMが与えられて、計算結果にmod Mを求める必要があり、かつ、計算過程に逆元を求める必要がある問題