Day 141-Q3 — Lucas の定理(mod pでの巨大二項係数計算)

2026-09-02 赤色 Master / Phase 8+ ★★★★★★★★★ N,K≤10^18でもp進法の桁分解でmod p計算

問題

非負整数 $N,K$($N,K\le10^{18}$)と素数 $p$($2\le p\le10^6$)が与えられる。

二項係数 $\binom{N}{K} \bmod p$ を求めよ($K>N$ のときは $0$)。

Lucas の定理を用いること。$N,K$ を $p$進法で表したときの桁を下位桁から順に $N=(n_0,n_1,\dots,n_r)_p$、$K=(k_0,k_1,\dots,k_r)_p$(足りない桁は0埋め)とすると、

$$\binom{N}{K} \equiv \prod_{i=0}^{r} \binom{n_i}{k_i} \pmod{p}$$

が成り立つ(ある $i$ で $k_i>n_i$ なら $\binom{n_i}{k_i}=0$ なので全体も $0$)。各桁の $\binom{n_i}{k_i}$ は $n_i,k_i

入力形式

N K p

制約

$0 \le K \le N \le 10^{18}$
$p$ は $2\le p\le10^6$ の素数

入出力例

入力例1

44 15 13

出力例1

4

入力例2

5000037100066 2000015050027 1000003

出力例2

668031

概念図

N=44, K=15, p=13 の桁分解 N = 44 = 3×13 + 5 → 桁 (n1,n0) = (3, 5) K = 15 = 1×13 + 2 → 桁 (k1,k0) = (1, 2) C(n1,k1) = C(3,1) = 3 C(n0,k0) = C(5,2) = 10 C(44,15) mod 13 = 3×10 mod 13 = 4

ヒント(段階的開示)

ヒント1(方向性)

$N,K\le10^{18}$では階乗を直接計算できない(法$p$が小さくても$N!$自体に$p$の倍数が大量に含まれ単純な階乗DPは使えない)。しかし$p$自体は最大でも$10^6$程度なので、「$0$から$p-1$までの階乗とその逆元」なら前計算できる。この小さい世界の二項係数だけを使って巨大な$N,K$の二項係数を求める橋渡しをするのがLucasの定理である。

ヒント2(アプローチ)

$N,K$を$p$進法表記に直すと各桁は必ず$[0,p-1]$に収まる。桁ごとに$\binom{n_i}{k_i}\bmod p$を計算し掛け合わせるだけで求まる。証明の直感は $(1+x)^N \equiv \prod_i (1+x^{p^i})^{n_i} \pmod p$(Freshman's dream: $(1+x)^p\equiv1+x^p \pmod p$)という多項式展開を経由するところにある。

ヒント3(誘導)
def precompute_fact(p):
    fact = [1]*p
    for i in range(1, p):
        fact[i] = fact[i-1]*i % p
    inv_fact = [1]*p
    inv_fact[p-1] = pow(fact[p-1], p-2, p)
    for i in range(p-1, 0, -1):
        inv_fact[i-1] = inv_fact[i]*i % p
    return fact, inv_fact

def small_comb(n, k, p, fact, inv_fact):
    if k < 0 or k > n:
        return 0
    return fact[n]*inv_fact[k]%p*inv_fact[n-k]%p

def lucas(n, k, p, fact, inv_fact):
    result = 1
    while n > 0 or k > 0:
        ni, ki = n % p, k % p
        c = small_comb(ni, ki, p, fact, inv_fact)
        if c == 0:
            return 0
        result = result * c % p
        n //= p
        k //= p
    return result

pow(fact[p-1], p-2, p)はフェルマーの小定理によるmod逆元計算($p$が素数であることが必須)。

模範解答 (Python)

import sys

def precompute_fact(p):
    fact = [1] * p
    for i in range(1, p):
        fact[i] = fact[i - 1] * i % p
    inv_fact = [1] * p
    inv_fact[p - 1] = pow(fact[p - 1], p - 2, p)
    for i in range(p - 1, 0, -1):
        inv_fact[i - 1] = inv_fact[i] * i % p
    return fact, inv_fact

def small_comb(n, k, p, fact, inv_fact):
    if k < 0 or k > n:
        return 0
    return fact[n] * inv_fact[k] % p * inv_fact[n - k] % p

def lucas(n, k, p, fact, inv_fact):
    result = 1
    while n > 0 or k > 0:
        ni = n % p
        ki = k % p
        c = small_comb(ni, ki, p, fact, inv_fact)
        if c == 0:
            return 0
        result = result * c % p
        n //= p
        k //= p
    return result

def main():
    n, k, p = map(int, sys.stdin.read().split())
    fact, inv_fact = precompute_fact(p)
    print(lucas(n, k, p, fact, inv_fact))

main()

Step-by-Step 解説

1なぜp進法の桁で分解できるのか
「$\bmod p$の世界では$(1+x)^p\equiv1+x^p$」という事実($\binom{p}{i}$が$0
2桁ごとの二項係数は前計算でO(1)
各桁$n_i,k_i$は必ず$[0,p-1]$に収まるため、階乗テーブルfactとその逆元テーブルinv_factを前計算しておけば$O(1)$計算できる。前計算自体は$O(p)$。
3桁数と全体の計算量
$N\le10^{18}$なので$p$進法の桁数は$O(\log_p N)$個($p=2$の最悪ケースでも60桁程度)。前計算$O(p)$と合わせて全体で$O(p+\log_p N)$。
4早期終了の最適化
ある桁で$k_i>n_i$になった時点で全体の積も必ず$0$になるため、その時点で計算を打ち切ってよい。
5検算例
$N=44,K=15,p=13$は$44=(3,5)_{13}$、$15=(1,2)_{13}$なので$\binom{44}{15}\equiv\binom31\binom52=3\times10=30\equiv4\pmod{13}$。実際に$\binom{44}{15}=229911617056$を13で割った余りも4に一致(実行検証済み)。

よくあるミス

ミス原因正しい書き方
大きい$N,K$に対しmath.comb(N,K) % pを直接計算しようとするLucasの定理を使わず愚直計算を試みる$N\le10^{18}$では非現実的な時間。必ず桁分解を使う
$p$が素数であることを確認せず合成数に適用してしまうフェルマーの小定理が素数専用であることを忘れる本問では$p$は素数と保証。合成数の場合は別途拡張が必要
桁分解のループでn//=p, k//=pを忘れ無限ループループ条件の更新漏れ毎回のループ末尾で必ず両方を$p$で割る
$k_i>n_i$の判定を外側で行わずIndexErrorになる配列アクセス前の範囲チェックを怠るsmall_comb冒頭で範囲チェックし0を返す
前計算のテーブルサイズを誤解するインデックスの数え方の混乱桁の値は$[0,p-1]$なのでサイズ$p$の配列で正しい

次のステップ

  • 発展: $p$が素数のべき乗$p^e$である場合の一般化(拡張Lucasの定理)を調べ、$\binom{N}{K}\bmod p^e$を求めるアルゴリズムを実装せよ。
  • 発展2: 法$M$が素数べきの積に分解できる合成数の場合、各素因数べきごとに拡張Lucasを適用しCRTで合成する処理を実装せよ。

自己評価

自分の回答

気づき・メモ