[백준] 11401번: 이항 계수 3
문제
https://www.acmicpc.net/problem/11401
풀이
먼저 두 가지 개념을 알아야 한다.
모듈로 곱셈 역원
다음 합동식을 만족하는 정수 를 의 모듈로 에 대한 곱셈 역원이라고 한다. 단, 와 은 서로소여야 한다.
다르게 해석하면, 와 의 곱을 으로 나눈 나머지가 1이 되게 하는 를 말한다.
페르마의 소정리
는 소수, 는 정수이며, 와 는 서로소일 때, 위 합동식을 만족한다.
모듈로 곱셈 역원과 페르마의 소정리를 통해 이항 계수를 계산해보자.
이항 계수
공식은 다음과 같다.
문제에서는 이항 계수의 결과를 1,000,000,007로 나눈 나머지를 구해야 한다. 라고 할 때, 다음 합동식을 만족한다.
모듈로 연산에서는 나눗셈이 정의되지 않으므로 의 역원을 곱하는 방식으로 작성해야 한다.
그렇다면 의 역원은 어떻게 구해야 하는가? 여기서 페르마의 소정리를 사용한다.
양변을 로 나눈다.
즉, 의 역원은 이다. 에 을 대입하면, 의 역원은 가 된다.
다시 이항 계수 공식에 대입하면, 아래와 같다.
다음 순서로 답을 계산한다.
- 를 구한다.
- 를 구한다.
- 2번 수를 번 거듭제곱한다. 이 때 분할 정복을 사용한다.
- 1번과 3번 수를 곱한 값을 다시 에 대한 모듈로 연산을 진행한다.
코드
C++
#include <iostream>
#define MOD 1000000007
using namespace std;
long long fact[4000001];
long long divconq(long long a, int r) {
if (r == 0) return 1;
long long half = divconq(a, r / 2);
long long ret = (half * half) % MOD;
if (r % 2 == 1) ret = (ret * a) % MOD;
return ret;
}
int main() {
int n, k;
cin >> n >> k;
fact[0] = 1;
for (int i = 1; i <= n; i++) {
fact[i] = fact[i - 1] * i;
fact[i] %= MOD;
}
cout << (fact[n] * divconq((fact[n - k] * fact[k]) % MOD, MOD - 2)) % MOD;
}
시간복잡도
팩토리얼 계산에서 , 분할 정복에서 이다. 은 약 30이므로, 사실 상 상수 시간 시간복잡도를 가진다고 할 수 있다.
공간복잡도
공간복잡도는 fact 배열에서 이다.
fact 배열은 long long 타입 4000001개의 데이터를 담을 수 있으므로, 8 X 4000001 = 약 30MB 크기를 가진다.
분할 정복에서 재귀 깊이는 깊지 않으므로 스택 메모리는 거의 차지하지 않는다.