先日やっていたバチャで出てきたので、まとめておきます。
この記事では二項係数を と表記します。また法を
とします。
はじめに
が必要となる
の最大値をそれぞれ
として、
や
が許される制約であれば、パスカルの三角形で全部計算できます。こちらのほうが圧倒的に楽なので間に合う場合はこちらを使いましょう。
vector<vector<int64_t>> comb(N+1, vector<int64_t>(N+1)); for(int i=0; i<=N; i++){ comb[i][0] = comb[i][i] = 1; for(int j=1; j<i; j++) comb[i][j] = (comb[i-1][j-1] + comb[i-1][j]) % M; }
やり方
アウトライン
中国剰余定理を使います。
中国剰余定理 (CRT) の解説と、それを用いる問題のまとめ - Qiita
求めたい答え(の を取る前の値)を
とします。
を求める代わりに、
を求めて、中国剰余定理で復元します。
例えば であれば、これは
と素数冪の積に分解できるので、
と
と
をそれぞれ別々に求めて復元します。
この が二項係数の足し・引き・掛け算で計算できるようなときに、
を求める方法を書いていきます。
二項係数
法 が十分大きい素数である場合は、階乗とその逆元を前計算しておいて
で
を求める方法が広く使われています。ただし
が素数でない場合や、
が小さくて
が
以上になってしまう場合は、一般には逆元が取れると限らないためこの方法は使えません。ですが、これを応用することにします。
素数を 、法とする素数冪を
と書くことにします。このとき
は、
の中の素因数
を全て取り除いたものを、
で計算したもの
の中に素因数
がいくつ含まれるか
の2つで特徴付けられます。これらの値を順に として、
と表記することにします。
例えば を法として
を計算する場合は、
の中に素因数
が
つ含まれ、それらを除くと
なので、
となります。
ここで の値が
の倍数になることはありません。例えば
として、素因数に
を含まない奇数だけを掛け算した値が、
で
になることはありません。
に対応する値
は、
と順次計算していくことができます。
から
を計算する際には、
に素因数
がいくつ含まれるかを計算して、
をそれぞれ処理すれば良いです。
そして二項係数
を で計算する際も、これらの値から計算することができます。
については、分母にあるものに対しては
での逆元を計算して掛ければ良いです。
は素数とは限らないので、フェルマーの小定理ではなく拡張ユークリッドの互除法を使いましょう。
において
が
の倍数になることはない、つまり
と互いに素であることは保証されるので、拡張ユークリッドの互除法は適用可能です。
「1000000007 で割ったあまり」の求め方を総特集! 〜 逆元から離散対数まで 〜 - Qiita
については、分母にあるものをマイナスとして扱って足し算すれば良いです。二項係数は整数になるので、この値が非負であることは保証されます。
こうして得られた二項係数が と計算できたとして、ここから
の値を求めます。これは
であれば
であり、そうでなければ
を実際に計算してあげれば良いです。
これで が計算できたので、これらの足し・引き・掛け算で構成される値は
で計算できます。
の形式から実際の
での値に直してしまった後は、逆元は一般には取れないので注意です。
長いですが実装例です。
// サブルーチン群 void add(int64_t& a, int64_t b, int64_t mod){ a = (a+b) % mod; } void mul(int64_t& a, int64_t b, int64_t mod){ a = a*b % mod; } vector<pair<int64_t, int>> prime_division(int64_t n){ vector<pair<int64_t, int>> ret; for(int64_t i=2; i*i<=n; i++){ int cnt = 0; while(n % i == 0){ n /= i; cnt++; } if(cnt) ret.emplace_back(i, cnt); } if(n > 1) ret.emplace_back(n, 1); return ret; } int64_t extgcd(int64_t a, int64_t b, int64_t& x, int64_t& y){ int64_t d = a; if(b != 0){ d = extgcd(b, a%b, y, x); y -= (a/b) * x; }else{ x = 1; y = 0; } return d; } int64_t inv_mod(int64_t a, int64_t mod){ int64_t x, y; extgcd(a, mod, x, y); return (MOD + x%mod) % mod; } // 素数冪を (p, c) で表現したもの vector<pair<int64_t, int>> primes; // 素数冪 p^c の実際の値 vector<int64_t> ppow; // 階乗を (x, y) 形式で表現したもの vector<vector<pair<int64_t, int>>> fact; // 素因数分解をして素数冪ごとにfactを前計算 void create_composite_mod_table(int N, int64_t M){ primes = prime_division(M); int sz = primes.size(); ppow.resize(sz, 1); fact.resize(sz); for(int pi=0; pi<sz; pi++){ int64_t p = primes[pi].first, cnt = primes[pi].second; while(cnt--) ppow[pi] *= p; auto& f = fact[pi]; f.resize(N+1); f[0] = {1, 0}; for(int i=1; i<=N; i++){ f[i] = f[i-1]; int n = i; while(n%p == 0){ n /= p; f[i].second++; } mul(f[i].first, n, ppow[pi]); } } } // 二項係数を計算 int64_t comb_mod(int n, int k, int pi){ auto &a = fact[pi][n], &b = fact[pi][k], &c = fact[pi][n-k]; int64_t p = primes[pi].first, cnt = primes[pi].second; int64_t pp = ppow[pi]; int pw = a.second - b.second - c.second; if(pw >= cnt) return 0; int64_t v = a.first; mul(v, inv_mod(b.first, pp), pp); mul(v, inv_mod(c.first, pp), pp); while(pw--) mul(v, p, pp); return v; }
問題例
Codeforces Div.1の問題です。