본문으로 건너뛰기
김신건의 로그

뤼카 정리 (Lucas Theorem)

· 수정 · 📖 약 4분 · 1,037자/단어 #algorithm #math #combinatorics #lucas
lucas theorem, lucas, 뤼카 정리, nCr mod p, lucas-theorem

정의

뤼카 정리 (Lucas Theorem) 는 소수 p 에 대해 큰 n, r 의 이항 계수 nCr 을 p 로 나눈 나머지를 O(log_p N) 에 구하는 방법. n, r 을 p 진수로 전개한 각 자릿수에 대해 조합을 곱한 값이 nCr mod p 와 같다.

n = n_k p^k + ... + n_1 p + n_0
r = r_k p^k + ... + r_1 p + r_0

nCr ≡ ∏ n_i C r_i (mod p)

문제 상황과 동기

nCr mod p 를 구해야 하는데 n, r 이 10^18 까지 클 때.

  • naive (Pascal dp): O(n^2). n=10^6 도 불가.
  • 팩토리얼 + 페르마 역원 (mod prime): O(n) 전처리. n < p 일 때만 작동. n >= p 면 분모에 p 의 배수가 들어가 역원 없음.
  • 뤼카 정리: O(log_p N) 자릿수 순회. n 이 p 이상이어도 OK.

핵심 통찰: n 을 p 진수로 쪼개면, 각 자릿수 조합의 곱이 전체 조합의 mod p 값. p 소수일 때 (1+x)^n 의 전개에 p 진수 분해를 적용 (생성함수 증명).

시각화

핵심 아이디어

p 가 소수일 때 (1 + x)^p ≡ 1 + x^p (mod p) 가 성립 (Frobenius endomorphism).

n = n_0 + n_1 p + n_2 p^2 + ... + n_k p^k

(1+x)^n = (1+x)^(n_0) · ((1+x)^p)^(n_1) · ((1+x)^(p^2))^(n_2) · ...
        ≡ (1+x)^(n_0) · (1+x^p)^(n_1) · (1+x^(p^2))^(n_2) · ... (mod p)

x^r 의 계수 = 각 자릿수에서 n_i C r_i 의 곱

알고리즘

nCr_mod_p(n, r, p):
    if r > n: return 0
    res = 1
    while n > 0 or r > 0:
        ni = n % p
        ri = r % p
        if ri > ni: return 0     # 자릿수 조합 = 0
        res = res * C_small(ni, ri) % p
        n /= p
        r /= p
    return res

C_small(n, r):    # n, r < p
    return fact[n] * inv_fact[r] * inv_fact[n-r] % p

구현

// Lucas Theorem, O(log_p N)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll modpow(ll a, ll b, ll m) {
  ll res = 1;
  while (b > 0) {
      if (b & 1) res = res * a % m;
      a = a * a % m;
      b >>= 1;
  }
  return res;
}

ll nCr_small(ll n, ll r, ll p) {
  if (r > n) return 0;
  ll num = 1, den = 1;
  for (ll i = 1; i <= r; i++) {
      num = num * (n - i + 1) % p;
      den = den * i % p;
  }
  return num * modpow(den, p - 2, p) % p;
}

ll lucas(ll n, ll r, ll p) {
  ll res = 1;
  while (n > 0 || r > 0) {
      ll ni = n % p, ri = r % p;
      if (ri > ni) return 0;
      res = res * nCr_small(ni, ri, p) % p;
      n /= p; r /= p;
  }
  return res;
}

int main() {
  ll n, r, p; cin >> n >> r >> p;
  cout << lucas(n, r, p) << "\n";
}
stdin
7 3 5
결과
1

복잡도

항목
시간O(log_p N) 자릿수 순회
공간O(1) (전처리 없음) / O(p) (팩토리얼 전처리)
p 제한소수여야 함

전처리로 p 이하 팩토리얼과 역원을 미리 구하면 각 자릿수 조합이 O(1). 자릿수 개수는 log_p N.

변형

변형설명
팩토리얼 전처리 버전p 가 작으면 O(p) 전처리 후 각 자릿수 O(1)
nCr mod p^k (Lucas 확장)Granville 의 일반화. p 소수 거듭제곱에 대해서도 동작
DP + LucasCatalan 수 등에 Lucas 적용
음이항 계수확장 Lucas (n < 0 지원)

함정

1. ri > ni 이면 0

if (ri > ni) return 0;  // 빼먹으면 잘못된 값

p 진수에서 r 의 자릿수가 n 의 자릿수보다 크면 그 조합은 0.

2. p 가 소수가 아닐 때

Lucas 정리는 p 가 소수일 때만 성립. 합성수 mod 에서는 확장 Lucas (Granville) 또는 중국인 나머지 정리 필요.

3. n == 0 or r == 0

r=0 이면 1 반환. 반복문에서 n, r 이 0 이 될 때까지 도므로 자동 처리됨 (n_i=0, r_i=0 → 0C0=1).

4. 64비트 오버플로우

곱셈에 res * nCr_small 에서 long long 범위 내. p^2 이 2^63 미만인지 확인.

BOJ 연습 문제

번호제목정답률링크
BOJ 11402이항 계수 4 (Lucas)-kokoa-lab
BOJ 16134조합 (Combination)-kokoa-lab
BOJ 15791세진이의 미팅-kokoa-lab
BOJ 13529Silver-Healthy-kokoa-lab

참고

이 글의 용어 (3개)
모듈러 역원 (Modular Multiplicative Inverse)algorithm
정의 정수 a 의 모듈러 역원 (modular multiplicative inverse) 은 을 만족하는 정수 x. 기호로 또는 . 존재 조건: gcd(a, m) = 1 일 때만…
FFT, NTTalgorithm
정의 FFT (Fast Fourier Transform, 고속 푸리에 변환) 은 길이 인 수열의 이산 푸리에 변환 (DFT) 을 O(n log n) 에 계산하는 분할정복 알고리즘…
Generating Functionalgorithm
정의 Generating Function (생성 함수) 은 수열 를 형식 멱급수 (formal power series) 으로 인코딩한 것. 수열의 산수 를 다항식 / 멱급수의 대…

이 개념을 다룬 위키 페이지 (1)

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기