BOJ) 11401. 이항 계수 3

(문제)

자연수 N과 정수 K가 주어졌을 때, 이항 계수 $N\choose K$를 1,000,000,007로 나눈 나머지를 구하는 프로그램을 작성하시오.

입력) 첫째 줄에 N과 K가 주어짐 $(1\leq N\leq 4000000, 0\leq K\leq N)$

출력) $N\choose K$를 1,000,000,007로 나눈 나머지를 출력한다.

조건) 시간 제한 1초, 메모리 제한 256MB


(풀이)

이항 계수를 알아내는 프로그램을 작성할 때, 우리는 어떠한 접근 방법이 있을까?
먼저, 가장 간단하게 생각할 수 있는 것은 이항 계수의 식을 이용하여 구하는 방식이다.

$$ \binom{N}{K} = \begin{cases} n!/(k!(n-k)!) & 0\leq K \leq N \\ 0 & K < 0 \\ 0 & K > N \end{cases}$$

이 식은 파스칼의 법칙에 따라 다음과 같은 점화식으로 재정의할 수 있다.
$$ \binom{N}{K} = \binom{N-1}{K-1} + \binom{N-1}{K} $$

따라서, N과 K가 작은 경우 메모이제이션 기법을 통해 다음과 같은 코드가 가능하다:

#include <stdio.h>
long long dy[10][10];
 
long long d(int a, int b){
    if(a<0 || b<0) return 0;
    if(dy[a][b]!=-1) return dy[a][b];
    if(a==b) return dy[a][b] = 1;
    if(b==1) return dy[a][b] = a;
    return dy[a][b] = (d(a-1,b-1) + d(a-1,b));
 
}
 
int main(){
    int N, K; scanf("%d%d",&N,&K);
    for(int i=0; i<=N; i++){
        for(int j=0; j<=K; j++){
            dy[i][j] = -1;
        }
    }
    printf("%lld",d(N,K));
}
 

하지만 이 방법에는 큰 단점이 있는데, N과 K가 급격히 커질 경우 실행 시간이 커진다는 점에 있다.

따라서, 본 문제에 접근하기 위해서는 1,000,000,007로 나눈 나머지를 출력한다는 점을 이용하면 쉽게 접근할 수 있다.

이 부분을 위해 먼저 이산수학에서 배우는 페르마의 소정리 내용을 되짚고 가자.

(페르마의 소정리)

먼저 p가 소수이고, a가 정수이며 p와 a가 서로소라 하자. 그럴 경우, a가 0 아닐 경우 다음과 같은 식이 성립한다.

$$a^{p-1}  \equiv 1  \text{(mod p)}$$

이는 다음과 같이 증명할 수 있다:

(1) a와 서로소인 소수 p에 대해 $\{ a, 2a, 3a, ..., (p-1)a \} $는 p로 나누었을 때의 나머지가 모두 다르다.

(2) 또한 a와 p는 서로소이므로 $ia$는 p의 배수가 아니다.

(3) 집합 $A = \{x|x = ia, i \in \{1, ..., p-1\} \}$과 $B = \{1,2, ..., p-1\}$를 보자. (1)에 의해 $|A| = |B|$이므로, 다음과 같은 식을 유도할 수 있다.
$$ a \times 2a \times 3a \times \cdots \times (p-1)a \equiv 1 \times 2 \times \cdots \times (p-1)  \text{(mod p)} $$
이때, 양변을 $(p-1)!$로 나누면,
$$a^{p-1} \equiv 1 \text{(mod p)}$$를 얻는다.

그렇다면 이 페르마의 소정리 내용을 어떻게 이용할 수 있을까?

modular p에 대하여 $1/(k! \times (n-k)!)$를 modular p에 대한 $k! * (n-k)!$의 역원을 구해주는 것으로 이용할 수 있다.

즉 $k! * (n-k)!$의 역원을 modular p에 대해 구해준 후, 이를 n!에 곱해주면 답을 계산해줄 수 있는 것이다.

그렇다는 것은 다음과 같은 수식을 유도할 수 있게 해준다:

$$ a \times a^{p-2} \equiv 1 (mod p) $$

이 때,  a가 $k! * (n-k)!$라면? 당연히 $a^{p-2}$가 $a$의 역원이 된다. 즉

$ (k! \times (n-k)!)^{p-2} \times n! \equiv n!/(k!(n-k)!) \text{(mod p)} $를 만족하게 되는 것이다.

이 때, p-2의 제곱을 구하는 것은 divide and conquer를 이용해주면 가능하다.


#include <stdio.h>
#define mod 1000000007LL
typedef long long ll;
 
ll fac[4000001], inv[4000001];
 
ll power(ll x, int p){
 ll temp = 1;
 while(p>0){
  if(p%2) temp = (temp * x) % mod;
  x = (x * x) % mod;
  p/=2;
 }
 return temp;
}
 
int main() {
 int N, K; scanf("%d%d",&N,&K);
 fac[0] = 1;
 for(int i=1; i<=N; i++)
  fac[i] = (fac[i-1] * i) % mod;
 printf("%lld", (power((fac[K]*fac[N-K])%mod,mod-2)*fac[N])%mod);
}

댓글

가장 많이 본 글