[동적계획법] 이항계수
이런 말이 있다.
동적 계획법이라는 말은 전문가들이 전문가들처럼 보여줄 수 있도록 해주는 말이고 일반인들에게는 그냥 ‘기억해서 풀기’ 다.
이항계수에 관련한 성질은 기억해두면 이후 코딩이나 알고리즘 문제를 풀 때 유용하기 때문에 기록해 준다. 이항계수를 풀 때 중요한 성질은 다음과 같다.
$$ {n \choose k} = {n \choose n-k} $$
$$ {n \choose k} = {n-1 \choose k} + {n-1 \choose k-1} $$
$$ \sum_{k=1}^n {n \choose k} = 2^n $$
위의 공식은 이항계수의 정의식을 참고해서 유도하는 방법으로 이항 계수의 정의식을 알고 있어야 한다.
$$ {n \choose k} = {n}\mathrm{C}{k} = \frac{n!}{(n-k)!k!} $$
동적 계획법을 활용한 이항계수 풀이
이항계수에 관련한 알고리즘 문제를 풀기 위해서 이항계수의 2번째 성질을 이용하기로 한다. 그 이유는 2번째 성질이 동적 계획법 활용에 알맞게 더 작은 부분으로 분할하여 정복 할 수 있는 성질을 잘 드러내고 있기 때문이다. 다음 방법을 사용해서 알고리즘을 풀어보자.
여기서 일반 재귀나 분할 정복보다 동적 계획법에 알맞게 진행하기 위해서 memoization을 사용한다.
//DAC
import java.util.Scanner;
class Main{
static long[][] value;
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
value = new long[n+1][k+1];
coef(n, k);
long result = value[n][k];
System.out.println(result);
}
public static void coef(int a, int b){
if (a==b){
value[a][b] = 1;
return;
}
if (b==0) {
value[a][b] = 1;
return;
}
else {
if(value[a-1][b] == 0)
coef(a-1, b);
if(value[a-1][b-1] == 0)
coef(a-1, b-1);
value[a][b] = value[a-1][b] + value[a-1][b-1];
return;
}
}
}
단순히 이항계수의 정의를 이용한 유도식을 재귀를 통해서 구현한 것이다. 다음과 같이 구현하면 작은 숫자들에 대해서는 충분히 답을 낼 수 있지만 숫자가 커지게 되면 할당해야 하는 배열의 크기가 기하급수적으로 커지게되고, 그 결과 값 또한 long 타입으로도 담을 수 없기 때문에 매우 제한적이다. 따라서 백준 11401에서는 이항계수를 소수인 1,000,000,007로 나눈 프로그램을 작성하도록 되어 있다. 그 연산에 대해서는 2가지 접근 방법이 있다.
우선, 왜 위에서 소개한 이항계수 정의식에 바로 % 1,000,000,007을 하지 않는지에 대한 이유를 짚고 넘어가야 한다. 이항계수 정의식은 분수꼴이기 때문에 소수 p로 % 연산을 했을 때 분자와 분모에 나뉘어서 적용되지 않는다.
$$ \frac{N!}{K!(N-K)!}%p\qquad \Longrightarrow \qquad \text{나뉘어서 적용 불가} $$
접근 방법 1. 확장 유클리드 알고리즘
확장 유클리드 알고리즘은 기본 원리로 유클리드 호제법으로 GCD를 구하는 것을 따라가며, 두 정수 A,B가 주어졌을 때, 베주 항등식인 $Ax+By=gcd(A,B)$ 에서 gcd(A,B)를 구하고 정수해 (x,y)를 구하는 알고리즘이다.
여기서 사용하고 싶은 확장 유클리드 알고리즘의 항을 써보면 다음과 같다.
$$ (AB^{-1}) % p $$
위에서 말했던 이항계수 정의식을 보면 $A$와 $B$에 각각 무엇이 대입되는지 알 수 있다. 여기서 확장 유클리드 알고리즘을 사용하는데, 확장 유클리드 알고리즘은 두 수의 최대공약수와 베주 항등식의 $x$와 $y$까지 구할 수 있는 알고리즘이다.
$$ Bx + py =1 \qquad (i) $$
$$ Bx \equiv 1 \pmod{p} \qquad (ii) $$
위의 (i)에서 확장 유클리드 알고리즘으로 정수해 $(x, y)$를 구할 수 있는데 그럼 다음 식에 대입하면 원하는 식을 구할 수 있다.
$$ (AB^{-1}) % p \= (AB^{-1} \cdot 1) %p\=(AB^{-1} \cdot Bx)%p\=Ax%p $$
결론은, 베주 항등식에서 구한 $x$와 정의식에서 정의한 $A$를 곱한 것을 $p$로 modular 하면 원하는 식을 구할 수 있다.
여기서 확장 유클리드 알고리즘을 이해하고 최종적으로 $x$를 구하는 것이 어렵게 느껴졌는데, 구현해보니 지나치게 길거나 복잡하지는 않았다. 재귀를 사용해서 구현하였다.
public static void euc(long p, long B) {
if(p%B > 0){
euc(B, p%B);
temp = y;
y = x - (p/B) * y;
x = temp;
} else {
x = 0;
y = 1;
}
}
[참고 자료]: https://onsil-thegreenhouse.github.io/programming/problem/2018/04/02/problem_combination/