중국인의 나머지 정리 (Chinese Remainder Theorem)
정의
중국인의 나머지 정리 (CRT) 는 다음과 같은 연립 합동식의 해가 유일 하게 존재함을 보장:
x ≡ a_1 (mod m_1)
x ≡ a_2 (mod m_2)
...
x ≡ a_k (mod m_k)
단, m_1, m_2, …, m_k 는 쌍마다 서로소 (pairwise coprime).
해는 M = m_1 · m_2 · … · m_k 에 대해 modulo M 로 유일:
x = Σ a_i · M_i · inv(M_i) (mod M)
M_i = M / m_i, inv(M_i) 는 M_i 의 modulo m_i 역원.
문제 상황과 동기
하나의 수 n 을 여러 mod 값으로 알고 있을 때 n 을 복원하라.
- naive (n = 0..M-1 순회): O(M). M 이 크면 불가능.
- CRT 공식 (closed form): O(k log M). 각 역원을 extgcd 로 O(log m_i).
핵심 통찰: 큰 mod 의 해는 각 작은 mod 의 해를 독립적으로 구하고 조합. M_i · inv(M_i) ≡ 1 (mod m_i) 가 “투영” 과 “복원” 을 담당.
시각화
핵심 아이디어
CRT 구성
M = Π m_i
M_i = M / m_i
t_i = M_i^(-1) (mod m_i) (즉 M_i · t_i ≡ 1 mod m_i)
x = Σ a_i · M_i · t_i (mod M)
검증: x ≡ a_i (mod m_i). 왜? M_i · t_i ≡ 1 (mod m_i) 이고, i ≠ j 에 대해 M_j 는 m_i 의 배수 (M_j = Π_{k≠j} m_k) 이므로 M_j ≡ 0 (mod m_i).
Garner’s Algorithm
CRT 를 반복문으로 구성: x_1 = a_1, x_{i+1} = x_i + M_i · ((a_{i+1} - x_i) · inv(M_i)) mod m_{i+1}. 큰 수 연산 없이 incremental.
비서로소 mod
m_i 가 pairwise coprime 이 아닐 때: 소인수분해 후 쪼개거나, extended CRT (exCRT) 사용.
알고리즘
crt(a[], m[], k):
M = 1
for i = 0..k-1: M *= m[i]
x = 0
for i = 0..k-1:
M_i = M / m[i]
t_i = extgcd(M_i, m[i]).x // M_i * t_i ≡ 1 (mod m[i])
x = (x + a[i] * M_i % M * t_i) % M
return x
구현
// 중국인의 나머지 정리 (pairwise coprime mod)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
tuple<ll, ll, ll> extgcd(ll a, ll b) {
if (b == 0) return {a, 1, 0};
auto [d, x1, y1] = extgcd(b, a % b);
return {d, y1, x1 - (a / b) * y1};
}
ll mod_inv(ll a, ll m) {
auto [d, x, y] = extgcd(a, m);
return (x % m + m) % m;
}
ll crt(vector<ll> a, vector<ll> m) {
ll M = 1, x = 0;
for (auto v : m) M *= v;
for (size_t i = 0; i < a.size(); i++) {
ll Mi = M / m[i];
ll ti = mod_inv(Mi % m[i], m[i]);
x = (x + a[i] * Mi % M * ti) % M;
}
return x;
}
int main() {
int k; cin >> k;
vector<ll> a(k), m(k);
for (int i = 0; i < k; i++) cin >> a[i] >> m[i];
cout << crt(a, m) << "\n";
return 0;
}3
2 3
3 5
2 723복잡도
| 항목 | 값 |
|---|---|
| 시간 | O(k log M) (k: 합동식 개수, M = Π m_i) |
| 공간 | O(k) 또는 O(1) (incremental) |
| 전제 조건 | m_i pairwise coprime |
변형 / 활용
| 방법 | 설명 |
|---|---|
| Garner’s Algorithm | Incremental, 중간 큰 수 연산 없음 |
| extended CRT (exCRT) | m_i 가 서로소가 아닐 때도 해결 |
| RSA-CRT | decryption 을 mod p, mod q 로 나눠 4x 속도 향상 |
| NTT (Number Theoretic Transform) | 3-prime NTT 로 큰 convolution |
| 보간 다항식 (Lagrange) | CRT 와 구조적 유사 (점-값 → 다항식) |
함정
1. mod 가 서로소가 아닌 경우
CRT 공식이 성립하지 않음. 해가 없거나, modulo lcm(m_i) 에서 여러 해 존재. exCRT 필요.
2. 64-bit 오버플로우
M = Π m_i 가 long long 범위 (≈ 9e18) 를 초과할 수 있음. Python 은 자동 bigint, C++ 은 __int128 또는 분할 연산.
3. 역원이 존재하지 않음
gcd(M_i, m_i) = 1 이어야 역원 존재 (pairwise coprime 가 보장). exCRT 는 이를 우회.
4. 음수 a_i 처리
a_i 를 modulo m_i 에서 양수로 정규화: a_i = (a_i % m_i + m_i) % m_i.
BOJ 연습 문제
| 번호 | 제목 | 정답률 | 링크 |
|---|---|---|---|
| BOJ 1476 | 날짜 계산 | (수집 안 됨) | kokoa-lab |
| BOJ 6064 | 카잉 달력 | (수집 안 됨) | kokoa-lab |
| BOJ 15717 | 떡파이어 | (수집 안 됨) | kokoa-lab |
| BOJ 11692 | 시그마 함수 | (수집 안 됨) | kokoa-lab |
참고
이 글의 용어 (4개)
- 모듈러 역원 (Modular Multiplicative Inverse)algorithm
- 정의 정수 a 의 모듈러 역원 (modular multiplicative inverse) 은 을 만족하는 정수 x. 기호로 또는 . 존재 조건: gcd(a, m) = 1 일 때만…
- 오일러 피 함수 (Euler's Totient Function)algorithm
- 정의 오일러 피 함수 φ(n) 는 1 이상 n 이하의 자연수 중 n 과 서로소인 수의 개수. 1763년 레온하르트 오일러가 도입. , 소수 p 에 대해 . 문제 상황과 동기 n …
- 확장 유클리드 호제법 (Extended Euclidean Algorithm)algorithm
- 정의 확장 유클리드 호제법 (Extended Euclidean Algorithm) 은 두 정수 a, b 에 대해 를 구하면서, 동시에 를 만족하는 정수 x, y 를 찾는 알고리즘…
- FFT, NTTalgorithm
- 정의 FFT (Fast Fourier Transform, 고속 푸리에 변환) 은 길이 인 수열의 이산 푸리에 변환 (DFT) 을 O(n log n) 에 계산하는 분할정복 알고리즘…
이 개념을 다룬 위키 페이지 (9)
- wiki수학 (Mathematics)
- wiki이산 k 제곱근 (Discrete Kth Root)
- wiki이산 로그 (Discrete Logarithm)
- wiki이산 제곱근 (Discrete Square Root / Tonelli-Shanks)
- wiki유클리드 호제법 (Euclidean Algorithm)
- wiki오일러 피 함수 (Euler's Totient Function)
- wiki확장 유클리드 호제법 (Extended Euclidean Algorithm)
- wiki페르마 소정리 (Fermat's Little Theorem)
- wiki정수론 (Number Theory)
💬 댓글