flowchart TD
Root["루트 노드"] --> L["왼쪽 서브트리: k개 잎"]
Root --> R["오른쪽 서브트리: n-k개 잎"]
L --> Lv["C(k)가지"]
R --> Rv["C(n-k)가지"]
Lv --> Sum["C(n) = 합(C(k) x C(n-1-k))"]
Rv --> Sum
n+1 개 잎 이진 트리를 루트에서 좌/우 분할하면 동일 점화식 유도.
응용 (모두 C_n 개)
구조
파라미터
개수
올바른 괄호 배열
n 쌍
C_n
완전 이진 트리
n+1 개 잎
C_n
볼록 다각형 삼각 분할
n+2 각형
C_n
Dyck 경로
길이 2n
C_n
스택 정렬 가능 순열
1..n
C_n
단조 격자 경로 (대각 이하)
n x n 격자
C_n
핵심 아이디어
DP 점화식
dp[0]=1dp[i]={∑k=0}{i−1}dp[k]⋅dp[i−1−k]
시간 O(N^2), 공간 O(N).
이항계수로 직접 계산
Cn=(2{)n}{n}⋅{1n+1}
모듈러 역원 (p 소수):
Cnmodp=(2{)n}{n}⋅(n+1)−1modp
알고리즘
점화식 반복
dp[0] = 1for i in 1..n: dp[i] = 0 for k in 0..i-1: dp[i] += dp[k] * dp[i-1-k]
비율 점화식 (정수 나눗셈, 단조로움 활용)
C_0 = 1C_n = C_{n-1} * 2*(2n-1) / (n+1)
항상 정수로 나누어 떨어짐 (귀납법으로 증명). 모듈러 없이 큰 수 계산 시 유용.
구현
// Catalan number: DP + 이항계수 두 방법#include <bits/stdc++.h>using namespace std;const long long MOD = 1e9 + 7;long long power(long long a, long long b, long long mod) { long long res = 1; a %= mod; for (; b; b >>= 1) { if (b & 1) res = res * a % mod; a = a * a % mod; } return res;}// 방법 1: DPvector<long long> catalan_dp(int n) { vector<long long> dp(n + 1, 0); dp[0] = 1; for (int i = 1; i <= n; i++) for (int k = 0; k < i; k++) dp[i] = (dp[i] + dp[k] * dp[i - 1 - k]) % MOD; return dp;}// 방법 2: 이항계수 + 모듈러 역원long long catalan_binom(int n) { vector<long long> fact(2 * n + 2); fact[0] = 1; for (int i = 1; i <= 2 * n + 1; i++) fact[i] = fact[i-1] * i % MOD; long long binom = fact[2*n] % MOD * power(fact[n], MOD-2, MOD) % MOD * power(fact[n], MOD-2, MOD) % MOD; return binom * power(n + 1, MOD - 2, MOD) % MOD;}int main() { auto dp = catalan_dp(10); for (int i = 0; i <= 10; i++) cout << "C(" << i << ") = " << dp[i] << "\n"; cout << "C(10) via binom = " << catalan_binom(10) << "\n";}
# Catalan number: DP + 비율 점화식MOD = 10**9 + 7def catalan_dp(n): dp = [0] * (n + 1) dp[0] = 1 for i in range(1, n + 1): for k in range(i): dp[i] = (dp[i] + dp[k] * dp[i - 1 - k]) % MOD return dpdef catalan_ratio(n): """C_n = C_{n-1} * 2(2n-1) / (n+1). 항상 정수로 나누어 떨어짐.""" c = 1 result = [1] for i in range(1, n + 1): c = c * 2 * (2 * i - 1) // (i + 1) result.append(c % MOD) return result# DP 방식dp = catalan_dp(10)for i, v in enumerate(dp): print(f"C({i}) = {v}")# 비율 점화식 (모듈러 없이 정확한 정수)rat = catalan_ratio(10)print("비율 점화식:", rat)
// Catalan number DPimport java.util.*;public class Main { static final long MOD = 1_000_000_007L; public static void main(String[] args) { int n = 10; long[] dp = new long[n + 1]; dp[0] = 1; for (int i = 1; i <= n; i++) { for (int k = 0; k < i; k++) { dp[i] = (dp[i] + dp[k] * dp[i - 1 - k]) % MOD; } } for (int i = 0; i <= n; i++) { System.out.println("C(" + i + ") = " + dp[i]); } }}
C_n = C_{n-1} * 2*(2n-1) / (n+1) 는 정수지만, 모듈러 연산 후 나눗셈은 역원 필요.
# 틀린 방법 (모듈러 후 나눗셈)c = c * 2 * (2*i-1) % MOD // (i+1) # WRONG# 올바른 방법 1: 모듈러 전에 정수 나눗셈 (정확히 나누어짐)c = c * 2 * (2*i-1) // (i+1) % MOD # OK# 올바른 방법 2: 모듈러 역원c = c * 2 * (2*i-1) % MOD * pow(i+1, MOD-2, MOD) % MOD # OK
3. 스택 정렬 불가능 순열 수
1..n 을 스택으로 정렬 가능한 순열: C_n 개. 불가능한 순열: n! - C_n 개.
4. 오버플로우
Python 은 자동 bigint, C++/Java 는 MOD 없이 long long 으로 C_18 부터 오버플로우.
💬 댓글