Day 171-Q5 — 2026-10-06 | 二項係数を合成数で割った余り(拡張 Lucas。素数冪ごとに p を抜いた階乗と Legendre の公式で求め、CRT で合わせる)

2026-10-06 赤色 Master (Phase 8+) ★★★★★★★★★ 数論(焦点帯 M3)。法が素数なら Lucas の定理で桁ごとに分けられた。法が $10^6$ のような合成数だと、$K!$ が法と互いに素でなく逆元が取れない。素数冪 $p^e$ ごとに「$p$ の因子」と「$p$ と互いに素な部分」を分けて扱う

問題

整数 $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 つ。

  1. Legendre の公式: $n!$ に含まれる $p$ の指数は $\sum_{i\ge1}\lfloor n/p^i\rfloor$。$\binom nk$ の指数はその差で、Kummer の定理により「$p$ 進で $k+(n-k)$ を足したときの繰り上がりの回数」に等しい
  2. $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}$$

  1. Gauss による Wilson の定理の一般化: $\prod_{p\nmid i\le p^e}i\equiv-1\pmod{p^e}$。ただし $p=2,e\ge3$ のときだけ $+1$(記憶。本解では表の最後の値をそのまま使うので、この事実に頼らずに済む)

概念図

M = 12 = 2² · 3 のとき C(6, 3) = 20 を求める流れ M を素因数分解12 = 4 · 3 p^e = 4(p = 2) Legendre: n=6 は 3+1、k=3 と n−k=3 は 1 ずつ → cnt = 2 cnt = 2 ≥ e = 2 → この成分は 0(20 は 4 の倍数) p^e = 3(p = 3) cnt = 2 − 1 − 1 = 0。(6!)_3 = 1·2·4·5·(2!)_3 = 80 (3!)_3 = 2 → 80 / (2·2) = 20 ≡ 2 (mod 3) CRT: x≡0 (4), x≡2 (3)x = 8 = 20 mod 12 (x!)_p = (±1)^⌊x/p^e⌋ · tab[x mod p^e] · (⌊x/p⌋!)_p。±1 は p^e までの互いに素な数の積(p=2, e≥3 だけ +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 解説

1法を素数冪に分ける

$M$ を試し割りで素因数分解する。各 $p^e$ について、tab($p$ と互いに素な数の累積積)と CRT の係数 coef($\bmod p^e$ で 1、他の素数冪で 0 になる数)を前計算する。

2$p$ の指数を数える

$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 になる。

3$p$ を抜いた階乗の比

同じループで 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) が取れる。

4CRT

$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、例 2 400000 999999、例 3 7 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$、原典の確認

自己評価

自分の回答

気づき・メモ