본문 바로가기
알고리즘/분할 정복

(c++) BOJ 11401번: 이항 계수 3

by korea_musk 2024. 1. 14.

https://www.acmicpc.net/problem/11401

 

11401번: 이항 계수 3

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

www.acmicpc.net

 

 


 

 

 

문제 개요

 

 

코드 작성이 어렵다기보다 페르마의 소정리를 이용하여 큰 수에 대한 거듭제곱을 수행하면 된다.

 

페르마의 소정리를 이 문제에 적용시키는 것이 관건이다.

 

먼저 이항 정리의 식을 알고 가야 한다.

 

 $$ \binom{N}{K} = \frac{n!}{(N-K)!K!} $$

 

그리고 페르마의 소정리를 간단하게 보면

 

$$ a^{p} \equiv a (mod \; p) $$

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

 

이 수식을 이항 정리에 적용해보면

 

$$ \frac{n!}{(N-K)!K!} = n! \times ((N-K)!K!)^{-1} $$

$$ ((N-K)!K!)\ast ((N-K)!K!)^{-2} \equiv  1(mod\; p) $$

$$ ((N-K)!K!)^{-2} \equiv  ((N-K)!K!)(mod\; p) $$

 

그래서 p가 1,000,000,007이라고 했을 때

 

$$ \binom{N}{K} \equiv  N! \ast ((N-K)!K!)^{p-2}  $$

 

이것이 성립하게 된다.

 

p-2번의 거듭제곱을 분할 정복으로 처리하면 된다.

 

 

 

코드

 

 

#include <bits/stdc++.h>
#define DIV 1000000007
using namespace std;

int n, k;
long long f_nk;
// 팩토리얼
long long fac(long long x) {
    if (x == 1 || x == 0) return 1;
    return (fac(x - 1) % DIV) * x % DIV ;
}
// 제곱
long long mult(long long m) {
    m %= DIV;
    return m * m % DIV;
}
// 분할 정복
long long prime(long long p) {
    if (p == 1) return f_nk;
    if (p % 2 > 0) return prime(p - 1) * f_nk % DIV;
    return mult(prime(p / 2)) % DIV;
}

int main(void) {
    cin >> n >> k;
    f_nk = fac(n-k) * fac(k) % DIV;
    long long fib_n = fac(n) % DIV;
    cout << (fib_n * prime(DIV-2) % DIV);
}

 

'알고리즘 > 분할 정복' 카테고리의 다른 글

(c++) BOJ 6549번 분할 정복  (0) 2024.01.15
(c++) BOJ 1629번: 곱셈  (0) 2024.01.13
(c++) BOJ 1780번: 종이의 개수  (1) 2024.01.13
(c++) BOJ 2630번: 색종이 만들기  (0) 2024.01.12
(c++) BOJ 11444번: 피보나치 수 6  (0) 2024.01.12

댓글