問題
整数 $M$ と $T$ 個の質問が与えられる。$j$ 番目の質問では整数 $N_j,K_j$ が与えられるので、二項係数 $\binom{N_j}{K_j}$ を $M$ で割った余りを求めよ。
入力形式
T M
N_1 K_1
N_2 K_2
...
N_T K_T
制約
| # | 条件 |
|---|---|
| 1 | $1\le T\le10^4$ |
| 2 | $1\le M\le10^6$ |
| 3 | $0\le K_j\le N_j\le10^{18}$ |
| 4 | 入力はすべて整数 |
入出力例
入力例1
3 12
6 3
10 5
1000000000000000000 1出力例1
8
0
4($\binom63=20$、$\binom{10}5=252=12\cdot21$、$10^{18}\equiv4\pmod{12}$)
入力例2
2 1000000
1000000000000000000 500000000000000000
999999999999999999 1出力例2
400000
999999入力例3
4 8
12 4
9 2
16 8
13 6出力例3
7
4
6
4($\binom{16}8=12870\equiv6\pmod8$。「$p^e$ までの $p$ と互いに素な数の積は $-1$」を $p=2$ にもそのまま使うと $2$ と誤る。例 1・3 は math.comb と一致、例 2 の 1 行目は Pascal の漸化式で検算)
背景と出典
この方法は「拡張 Lucas」と呼ばれ、Andrew Granville の 1997 年の解説「Arithmetic properties of binomial coefficients I」にある素数冪での Lucas の一般化が元になっている、と私は記憶している(本セッションでは原典を参照していない)。本問は自作。
使う事実は 3 つ。
- Legendre の公式: $n!$ に含まれる $p$ の指数は $\sum_{i\ge1}\lfloor n/p^i\rfloor$。$\binom nk$ の指数はその差で、Kummer の定理により「$p$ 進で $k+(n-k)$ を足したときの繰り上がりの回数」に等しい
- $p$ を抜いた階乗 $(n!)_p$($1..n$ から $p$ の因子をすべて取り除いた積)は
$$(n!)_p\equiv\Bigl(\prod_{\substack{1\le i\le p^e\\ p\nmid i}}i\Bigr)^{\lfloor n/p^e\rfloor}\cdot\prod_{\substack{1\le i\le n\bmod p^e\\ p\nmid i}}i\cdot(\lfloor n/p\rfloor!)_p\pmod{p^e}$$
- Gauss による Wilson の定理の一般化: $\prod_{p\nmid i\le p^e}i\equiv-1\pmod{p^e}$。ただし $p=2,e\ge3$ のときだけ $+1$(記憶。本解では表の最後の値をそのまま使うので、この事実に頼らずに済む)
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
$M=\prod p^e$ と素因数分解し、各 $p^e$ で余りを求めて中国剰余定理(CRT)で合わせる。$p^e\le10^6$ なので、$p^e$ までの表は作れる。
ヒント2(アプローチ)
$\binom nk=p^{c}\cdot\dfrac{(n!)_p}{(k!)_p\,((n-k)!)_p}$。$c$ は Legendre の公式、分数の部分は $p$ と互いに素なので $p^e$ を法に逆元が取れる。$c\ge e$ ならこの成分は 0。
ヒント3(誘導)
tab[i] = $1..i$ のうち $p$ で割れない数の積 mod $p^e$。$(x!)_p$ は「tab[x % pe] を掛け、$\lfloor x/p^e\rfloor$ の偶奇で tab[pe] を掛け、$x\leftarrow\lfloor x/p\rfloor$」を $x=0$ まで繰り返す。$n,k,n-k$ の 3 つは同じ回数だけ $p$ で割るので、1 つのループにまとめられる。
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
def factorize(m):
fs = []
p = 2
while p * p <= m:
if m % p == 0:
e = 0
while m % p == 0:
m //= p
e += 1
fs.append((p, e))
p += 1
if m > 1:
fs.append((m, 1))
return fs
def main():
data = sys.stdin.buffer.read().split()
t, M = int(data[0]), int(data[1])
parts = []
for p, e in factorize(M):
pe = p ** e
# tab[i] = (1..i のうち p で割れない数の積) mod p^e
tab = [1] * (pe + 1)
acc = 1
for i in range(1, pe + 1):
if i % p:
acc = acc * i % pe
tab[i] = acc
# CRT の係数: x ≡ r (mod p^e), x ≡ 0 (mod M/p^e) となる x = r * coef
other = M // pe
coef = other * pow(other, -1, pe) % M if pe > 1 else 0
parts.append((p, e, pe, tab, coef))
out = []
idx = 2
for _ in range(t):
n, k = int(data[idx]), int(data[idx + 1])
idx += 2
if k < 0 or k > n or M == 1:
out.append(0)
continue
ans = 0
r_ = n - k
for p, e, pe, tab, coef in parts:
# n!, k!, (n-k)! から p を除いた値を同じループで求める。同時に Legendre の公式で p の指数 cnt を数える
# x!_p = (±1)^(x // p^e) * tab[x % p^e] * (x // p)!_p
num = den = 1
cnt = 0
flip = 0
a, b, c = n, k, r_
while a:
num = num * tab[a % pe] % pe
den = den * tab[b % pe] * tab[c % pe] % pe
flip += a // pe - b // pe - c // pe
a //= p; b //= p; c //= p
cnt += a - b - c
if cnt >= e:
continue # p^e で割り切れる → この成分は 0
if flip & 1 and tab[pe] != 1: # p^e までの互いに素な数の積は ±1(p=2, e≥3 だけ +1)
num = pe - num
r = num * pow(den, -1, pe) * pow(p, cnt, pe) % pe
ans = (ans + r * coef) % M
out.append(ans)
sys.stdout.write("\n".join(map(str, out)) + "\n")
main()
計算量: 前処理は $\sum p^e\le M$ の表づくりで $O(M)$、素因数分解は $O(\sqrt M)$。質問 1 つあたり $\sum_{p\mid M}\log_pN$ 回のループ($M=720720=2^4\cdot3^2\cdot5\cdot7\cdot11\cdot13$ で約 180 回)と逆元 1 回ずつ。$T=10^4$ で 0.13〜0.54 s(Python 3.14.7、M1 Mac、負荷平均 5〜6 のときの実測)。
Step-by-Step 解説
$M$ を試し割りで素因数分解する。各 $p^e$ について、tab($p$ と互いに素な数の累積積)と CRT の係数 coef($\bmod p^e$ で 1、他の素数冪で 0 になる数)を前計算する。
$a=n,b=k,c=n-k$ を同時に $p$ で割りながら、cnt += a - b - c を足す(Legendre の公式の差)。$p^e$ の成分は cnt >= e なら 0。$M=10^6=2^6\cdot5^6$ だと $\binom{10^{18}}{k}$ の多くはここで 0 になる。
同じループで num *= tab[a % pe]、den *= tab[b % pe] * tab[c % pe] を掛け、flip に $\lfloor a/p^e\rfloor-\lfloor b/p^e\rfloor-\lfloor c/p^e\rfloor$ を足す。最後に tab[pe]($=\pm1$)が $-1$ で flip が奇数なら符号を反転する。den は $p$ と互いに素なので pow(den, -1, pe) が取れる。
$r\equiv\text{num}\cdot\text{den}^{-1}\cdot p^{\text{cnt}}\pmod{p^e}$ を coef 倍して足し、$M$ で割る。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
math.comb(N, K) % M | 答えの桁数に比例 | 検算用のみ | $N\le3000$ の 33600 問の正解に使用。$N=10^{18}$ では不可能 |
階乗の表と逆元 pow(K!(N-K)!, -1, M) | $O(N)$ | 不採用 | 法が合成数だと逆元が無い。3300 問中 3296 問で例外(実測。$N\ge7$ の素数 7 も含む) |
| Lucas の定理を合成数 $M$ にそのまま使う | $O(\log_MN)$ | 不採用 | 33600 問中 3554 問誤答(実測) |
| 拡張 Lucas、$(x!)_p$ を 3 回別々に再帰(最初の版) | 同じ | 不採用 | 正しい(33600 問一致)が、$M=720720$ で 1.34 s・$M=2^{19}$ で 0.96 s(実測) |
| 拡張 Lucas、3 つを 1 ループにまとめ符号を偶奇で処理(本解) | $O(\sum_p\log_pN)$/問 | 採用 | 同じ入力で 0.53 s・0.34 s |
前提が変わったら:
- $M$ が $10^{18}$ 級なら、$p^e$ の表が作れない。$p^e$ が大きい成分は「$(x!)_p$ を多項式で高速に評価する」別の手法が要る(本セッションでは扱わない)
- $M$ が素数なら、ふつうの Lucas($p\le10^6$ なら階乗表)で足りる。本解も $e=1$ でそれと同じ計算になる(素数 6 種 1800 問で Lucas と一致、実測)
- $M$ が固定でなく質問ごとに変わるなら、表を毎回作るので $O(T\cdot M)$ になる。$M$ の種類ごとに質問をまとめる
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 合成数の法で $K!$ の逆元を取る | 法が素数のときの癖 | 素数冪に分け、$p$ を抜いた部分だけ逆元を取る |
| Lucas の定理を合成数 $M$ にそのまま使う | 「法の進数で桁ごと」が素数でしか成り立たない | 33600 問中 3554 問誤答(実測) |
| $p^e$ までの積を常に $-1$ とする | Wilson の定理の一般化の例外($p=2,e\ge3$)を忘れる | 表の最後の値を使う(33600 問中 972 問誤答、実測。入力例 3) |
| $p^{\text{cnt}}$ を掛け忘れる | $p$ を抜いた比だけで終える | 33600 問中 13879 問誤答(実測) |
| Legendre の和を $n$ だけで数える | $k$ と $n-k$ の分を引き忘れる | 3 つを同じループで割って差を足す |
次のステップ
- 発展1: $M$ が質問ごとに変わる版。$M$ の種類ごとにまとめる処理を書き、時間を測る
- 発展2: Kummer の定理だけを使い、「$\binom nk$ が $p^e$ で割り切れるか」を $O(\log_pN)$ で答える
- 発展3: Granville の原典で素数冪の Lucas 型の定理(桁ごとの積に分ける形)を確かめ、本解との関係を整理する(本セッションでは未確認)
検証ログ(本セッションで実行した数値)
math.combとの照合(実測): 法 30 種($1$、$2^k$、$3^k$、$5^8$、$7^7$、$97^2$、$720720$、$10^6$、$999983$、$510510$ など)× 1120 問($N\le3000$ のランダム 300 問+$N<40$ の全組 820 問)= 33600 問で不一致 0- 大きい $N$ の検算(実測): $N\in[10^{17},10^{18}]$ で Pascal の漸化式 $\binom nk\equiv\binom{n-1}{k-1}+\binom{n-1}k$ を法 7 種 × 300 組 = 2100 組で確認、不一致 0。素数の法 6 種(2, 3, 5, 7, 65537, 999983)では独立に書いた Lucas と 1800 問一致
- 誤った方法(実測): 同じ 33600 問で、積を常に $-1$ 972 問・$p^{\text{cnt}}$ 抜け 13879 問・合成数への Lucas 3554 問誤答。逆元を取る方法は 3300 問中 3296 問で逆元が存在しない
- 入力例(実測): 例 1
8 0 4、例 2400000 999999、例 37 4 6 4(例 1・3 はmath.combと一致。例 2 の 1 行目は $\binom{n-1}{k-1}\equiv\binom{n-1}{k}\equiv200000$ で、和が Pascal の漸化式どおり 400000 になる) - 実行時間(実測、負荷平均 5〜6、$T=10^4$、$N\in[10^{17},10^{18}]$): $M=2^{19}$(繰り上がりの無い $K$ を選び全問で計算が最後まで走るケース)0.34 s、$M=3^{12}$ 同 0.25 s、$M=720720$ 同 0.53 s、$M=10^6$ ランダム 0.31 s、$M=999983$ 0.13 s。負荷平均 36 のときは 1.0〜1.8 s
- 扱っていない範囲: $M>10^6$、質問ごとに変わる $M$、原典の確認