(kn)modMを求めたいが、Mが合成数だったりk≥Mだったりして階乗の逆元が得られない場合について。nが小さければパスカルの三角形を作ればよいが、Mが小さい場合にも方法がある。https://discuss.codechef.com/t/your-approach-to-solve-sandwich/14618/13 より。
Mをいくつかの互いに素な因数に分解してそれぞれを法として二項係数を求めれば、CRTでmodMの値が得られる。したがって、次の問題が解ければよい:
問題: 素数の累乗peが与えられた時に(kn)modpeを求めよ。
f(x,p):= 階乗x!を非負整数e,qを使ってx!=peqと表す時の最大のq
とする。言い換えると、f(x,p)はx!をpで割れる限り割ったものである。n!=pe1f(n,p),k!=pe2f(k,p),(n−k)!=pe3f(n−k,p)とするとき、二項係数(kn)は次のように書ける:
(kn)=f(k,p)f(n−k,p)pe1−e2−e3f(n,p)
f(k,p),f(n−k,p)はpeと互いに素なのでmodpeで逆元を持つ。したがって、e1,e2,e3とf(n,p),f(k,p),f(n−k,p)が求まれば二項係数を計算できる。
前者、つまりe1,e2,e3を求めるほうは簡単で、一般に階乗x!がpで割れる回数は∑i=1∞⌊x/pi⌋に等しく(ルジャンドルの公式)、この式で素朴に計算すればO(logx)で求まる。
次にmodpeでf(x,p)を計算する問題を考える。f(x,p)はx!から素因数pを除いたものなので、ひとまず階乗からpの倍数だけ分離して考えてみる: Xを1,2,...,xのうちのpの倍数でない数の総積とするとき、f(x,p)=Xf(⌊x/p⌋,p)が成り立ち、O(logx)回の再帰でf(0,p)=1に帰着してf(x,p)が求まる。 (実際にはループでよい。)
あとはXをmodpeで求める方法が問題になるが、pの倍数の位置はmodpeで周期的なことを利用できる。最初に「pの倍数だけ飛ばした階乗modpe」の長さpeのテーブルを作っておけば、Xは繰り返し二乗法でO(logx)で計算できる。
全体の計算量は
- build: O(pe)
- query: O((logn)2)
と評価できる。また、冒頭の任意のMを法とした二項係数を求める問題に関しては、
- build: O(M)
- query: O((logn)2loglogM)
となる。(loglogMはMの異なる素因数の個数の評価。)
Lucasの定理の素数の累乗に関する拡張を使う方法もあるらしい。たぶんこちらのほうが速い。
https://arxiv.org/pdf/1301.0252.pdf