モジュロ演算しながら四則演算する
これは何か
計算結果をで割った値を回答とする問題が頻出する.ARC058B,CODE FESTIVAL 2017 Final (Parallel) G,ABC145Dなどが該当する.AtCoderだけ見ても他にもたくさんあると思われるのだが,いかんせん問題文をすべて全文検索する方法がないので完璧にリストアップできないのが残念だ.
それはともかく,このような問題を解く場合はモジュロ演算の定石を知らないと即「詰み」になる演算があるため,事前に勉強しておく必要がある.本稿では対処法をまとめておく.
先に結論
証明で頻繁に必要になるので覚えておくべき合同式の性質
- (aとmが互いに素である場合,aは法mにおいて可逆)
四則演算の計算方法
演算 | 計算方法 |
---|---|
加算 | 一度演算を行うごとにMODを取る |
減算 | 一度演算を行うごとにMODを取る.最終結果が負の数の場合は法を足す. |
乗算 | 一度演算を行うごとにMODを取る. |
除算 | 割る数と法が互いに素である場合,割る数の逆元をかけていく. |
注意事項
代入演算子を使うとMODが先に評価されてしまい,意図した通りの結果にならないため使わない
- BAD
a += b % MOD
- GOOD
a = a + b % MOD
逆元の求め方
を求める場合,であればが成立するため,この不定方程式を解いて,特殊解のペアのうち1つのが逆元となる.
- 逆元はユークリッドの互除法でも解けるが,計算量を削減するために拡張ユークリッド互除法が一般に使用されるらしい.
- 参考
実装例(上述のQiita記事より)
#include <iostream> using namespace std; typedef long long ll; int const MOD = 1000000007; ll modinv(ll a, ll m) { ll b = m; ll u = 1; ll v = 0; ll t = a / b; while (b) { t = a / b; a -= t * b; swap(a, b); u -= t * v; swap(u, v); } u %= m; if (u < 0) u += m; return u; } int main() { ll a = 12345678900000; ll b = 100000; a %= MOD; cout << a * modinv(b, MOD) % MOD << endl; return 0; }
二項係数
の計算をする場合はメモ化により階乗の計算量を削減できる.
- 実装例(上述のQiita記事より)
#include <iostream> #include <vector> using namespace std; const int MAX = 510000; const int MOD = 1000000007; typedef long long ll; vector<ll> fac(MAX, 0ll); vector<ll> finv(MAX, 0ll); vector<ll> inv(MAX, 0ll); void COMinit() { fac[0] = fac[1] = 1; finv[0] = finv[1] = 1; inv[1] = 1; for (int i = 2; i <= MAX; i++) { fac[i] = fac[i - 1] * i % MOD; inv[i] = MOD - inv[MOD % i] * (MOD / i) % MOD; finv[i] = finv[i - 1] * inv[i] % MOD; } } long long COM(int n, int k) { if (n < k) return 0; if (n < 0 || k < 0) return 0; // n! / (r!(n-r)!) return fac[n] * (finv[k] * finv[n - k] % MOD) % MOD; } int main() { COMinit(); cout << COM(510000, 123123) << endl; }
資料
- これにすべて書いてあるため,この内容を頭に叩き込んでおけば良さそう. qiita.com