알고리즘/문제 풀이
11401번: 이항 계수 3
Themion
2022. 1. 5. 16:09
https://www.acmicpc.net/problem/11401
11401번: 이항 계수 3
자연수 \(N\)과 정수 \(K\)가 주어졌을 때 이항 계수 \(\binom{N}{K}\)를 1,000,000,007로 나눈 나머지를 구하는 프로그램을 작성하시오.
www.acmicpc.net
N과 K의 범위가 매우 크므로 페르마의 삼각형을 사용할 수는 없으므로, 이항계수 nCk = n! / (k! * (n - k)!)를 이용해야 한다. 단 n!, k!, (n - k)! 역시 매우 큰 값이므로, 각각을 MOD = 1,000,000,007로 나눈 값을 저장해야 한다.
각 팩토리얼을 한 값에 모듈러 연산을 취했기 때문에, n! / (k! * (n - k)!) 공식을 그대로 이용할 수는 없다. 따라서 페르마의 소정리, 즉 소수 p와 p의 배수가 아닌 자연수 a에 대해, a ^ (p - 1) ≡ 1 (mod p)를 이용해야 한다. a를 p - 1, 즉 MOD번 제곱한 값이 1이 되므로, a ^ (p - 2)는 a에 대한 곱셈의 역원이 된다.
k!와 (n - k)!를 MOD로 나눈 값 kf, nkf는 MOD의 배수가 아니므로 kf * nkf 역시 MOD의 배수가 아니므로, 페르마의 소정리에 의해 (kf * nkf) ^ (MOD - 2)는 (kf * nkf)에 대한 곱셈의 역원, 즉 1 / (kf * nkf)와 같다. 따라서 이 값을 N!을 MOD로 나눈 값 nk와 곱해 MOD로 나누면 구하고자 하는 값을 얻을 수 있다.
#include <cstdio>
typedef unsigned long long ull;
#define MOD 1000000007
// (N ^ (MOD - 2))를 구하는 함수
ull pow(ull N) {
// a: N의 2 ^ k승, ans: N ^ (MOD - 2)를 저장할 공간
ull a = N % MOD, ans = 1;
// x를 이진법으로 보았을 때 x의 각 자리에 대해
for (int x = MOD - 2; x; x /= 2) {
// 현재 자리가 1이라면 ans에 a를 곱한다
if (x % 2) ans = (ans * a) % MOD;
// a를 제곱한다
a = (a * a) % MOD;
}
// N을 (MOD - 2)제곱한 결과를 반환
return ans;
}
int main() {
// 이항 계수의 N, K
int N, K;
// nf: N!, kf: K!, nkf: (N - K)!
ull nf = 1, kf = 1, nkf = 1;
// N과 K를 읽어들인다
scanf("%d %d", &N, &K);
// N!, K!, (N - K)!를 MOD로 나눈 나머지를 구한다
for (ull i = 2; i <= N; i++) {
nf = (nf * (i % MOD)) % MOD;
if (i == K) kf = nf;
if (i == N - K) nkf = nf;
}
// N!과 K!, (N - K)!를 구한 값을 MOD로 나눈 나머지만 알고 있으니
// 일반적인 이항 계수처럼 계산할 수 없다
// 페르마의 소정리, 즉 소수 p에 대해 a ^ (p - 1) = 1 (mod p)에 따라
// (K! * (N - K)!) ^ (MOD - 2) % MOD = 1 / (K! * (N - K)!)이므로
// 이 결과를 계산해 N!과 곱한 값을 반환
printf("%lld\n", (nf * pow(kf * nkf)) % MOD);
return 0;
}