ABC 156 D - Bouquet
ABC 156 D - Bouquet に関連する、逆元、二項係数についてのメモを残します。
参考
- 「1000000007 で割ったあまり」の求め方を総特集! 〜 逆元から離散対数まで 〜 - Qiita
- よくやる二項係数 (nCk mod. p)、逆元 (a^-1 mod. p) の求め方 - けんちょんの競プロ精進記録
mod の世界における "逆数"
はじめに、mod とは何なのか、については、
この動画で解説されています。
mod の世界で割り算することを考えたい。
実数の世界では、 を計算するとき、 という掛け算に変えることができる。
mod の世界での割り算も、mod の世界における "逆数" を用意して掛け算に変えたい。
1 次方程式 において、
を満たす (これを 逆元 という) が存在するならば、
から となる。
このように、 で割ることは、 を掛けることに変えることができる。
では、 はどう求めるのか。
ここで、フェルマーの小定理を利用する。
これより となり、 の逆元が であることがわかる。
つまり の世界において、 で割ることは、 を掛けることに変えることができる。
整数 は素数 で割り切れない、という制約を忘れず。
逆元を利用して を計算する実装を考える。
であるから、前処理として階乗、階乗の逆元をメモしておけば、 を で計算できる。
では、 の順にメモしていく。
階乗のメモの説明は省略。
階乗の逆元は、 の関係から順に求まるが、 の逆元の計算が必要である。
における の逆元 は、繰り返し自乗法により で計算できるので、都度計算することにする。
ちなみに、 の逆元を拡張 Euclid の互除法により で計算できるらしく、
この記事で解説されています。
以下、ABC 034 C - 経路 の提出コード。
問題概要 : を出力
#include <bits/stdc++.h> using namespace std; // 階乗、階乗の逆元のメモ vector<long long> fac, fac_inv; // a^b mod p を返す (繰り返し自乗法) long long modpow(long long a, long long b, long long p) { if (b == 0) return 1; long long res = modpow(a, b / 2, p); if (b % 2 == 0) res = (res * res) % p; else res = (((res * res) % p) * a) % p; return res; } // mod p における a の逆元 a^{p-2} mod p を返す long long inverse(long long a, long long p) { return modpow(a, p-2, p); } // nCk (mod p) の前処理 O(n log p) void comInit(int n, int p) { fac.resize(n+1); fac_inv.resize(n+1); fac.at(0) = fac.at(1) = 1; fac_inv.at(0) = fac_inv.at(1) = 1; for (int i = 2; i <= n; i++) { fac.at(i) = fac.at(i-1) * i % p; fac_inv.at(i) = fac_inv.at(i-1) * inverse(i, p) % p; } } // nCk mod p を返す O(1) long long com(int n, int k, int p) { if (n < k || n < 0 || k < 0) return 0; return fac.at(n) * (fac_inv.at(k) * fac_inv.at(n-k) % p) % p; } int main() { const int MOD = 1000000007; int W, H; cin >> W >> H; comInit(W+H, MOD); // 前処理 cout << com(W+H-2, W-1, MOD) << endl; // W+H-2_C_W-1 (mod MOD) }
- 問題 : ABC 156 D - Bouquet
出力すべき値は、
先ほどのコードは前処理の計算量が であるため、この問題のように が最大で となる場合には使えない。
しかし、 は固定であり、 は最大でも と小さいため、
とみることで、
のようにして、 の順にメモしていけばよい。
#include <bits/stdc++.h> using namespace std; // com[k] := nCk の値をメモ vector<long long> com; // a^b mod p を返す long long modpow(long long a, long long b, long long p) { if (b == 0) return 1; long long res = modpow(a, b / 2, p); if (b % 2 == 0) res = (res * res) % p; else res = (((res * res) % p) * a) % p; return res; } // mod p における a の逆元 a^{p-2} mod p を返す long long inverse(long long a, long long p) { return modpow(a, p-2, p); } // nCk (mod p) の前処理 O(k log p) void comInit(long long n, long long k, long long p) { com.resize(k+1); com.at(0) = 1; long long tmp = 1; for (int i = 1; i <= k; i++) { tmp = ((tmp * (n-i+1) % p) * inverse(i, p)) % p; com.at(i) = tmp; } } // val を p で割った余り (>= 0) を返す long long mod(long long val, long long p) { long long res = val % p; if (res < 0) res += p; return res; } int main() { const int MOD = 1000000007; int n, a, b; cin >> n >> a >> b; comInit(n, 200000, MOD); // 前処理 long long ans = modpow(2, n, MOD); cout << mod(ans - 1 - com.at(a) - com.at(b), MOD) << endl; }