문제
다음 형태의 점화식을 생각하자.
$$ f_n = \sum_{k=1}^{n} g_k \, f_{n-k} \quad (n \ge 1),\qquad f_0 = 1. $$
\(f_n\)을 계산하려면 \(f_0, \dots, f_{n-1}\)이 필요하고, 그 자체가 합성곱이다. 게다가 \(g_k\)가 \(f\)에 의존하는(자기 참조) 경우도 흔하다. 예: \(f = \exp(g)\)의 항별 점화, 트리/카탈란류 카운팅.
순진하게 각 \(n\)마다 합을 계산하면 \(O(n^2)\). 온라인(relaxed) 합성곱 은 이를 \(O(n \log^2 n)\)으로 낮춘다 — 단, \(f_n\)을 계산하는 시점에 \(f_{
왜 보통 FFT가 안 되나
보통 FFT 합성곱은 두 완성된 다항식을 곱한다. 하지만 여기선 \(f_n\)을 구할 때 \(f_n\) 자신이 아직 없다(\(g\)가 \(f\)에 의존하면 더더욱). 오프라인으로 전체를 한 번에 곱을 수 없다. 그래서 분할정복 + 부분 기여 누적 이 필요하다.
CDQ 분할정복 아이디어
구간 \([l, r)\)의 \(f\)를 모두 구하는 solve(l, r)을 재귀로 정의한다.
solve(l, r):
if r - l == 1:
f[l] 확정(이미 누적된 기여 + 자기 항)
return
mid = (l + r) / 2
solve(l, mid) # 왼쪽 먼저 확정
왼쪽 f[l..mid)가 오른쪽 f[mid..r)에 주는 기여를 FFT로 더함
solve(mid, r)
핵심: solve(l, mid)로 왼쪽 절반이 확정 된 뒤, 그 값들이 오른쪽 절반에 주는 합성곱 기여를 미리 더해 둔다. 오른쪽으로 내려가기 전에 더하므로 인과율이 지켜진다.
기여 합산의 형태
\(f_n\)(\(n \in [mid, r)\))에 대한 왼쪽 기여는
$$ \sum_{k=l}^{mid-1} g_{n-k}\, f_k \quad\text{또는}\quad \sum f_k\, g_{n-k} $$
형태(점화식에 따라). 이것을 두 배열 \(\{f_k\}_{k \in [l, mid)}\)와 \(\{g_j\}\)의 부분 합성곱으로 보고 NTT 한 번에 처리한다. 단, \(g\)가 \(f\)에 의존하면 \(g\)도 "확정된 범위"만 사용한다(대칭 처리: \(f \times g\)와 \(g \times f\) 양쪽 기여).
복잡도 분석
분할정복 깊이 \(\log n\). 각 깊이에서 모든 구간의 NTT 비용 합이 \(O(n \log n)\). 따라서
$$ T(n) = 2T(n/2) + O(n \log n) = O(n \log^2 n). $$
대안: relaxed (online) 방식
CDQ는 "구간을 미리 안다"는 점에서 반-오프라인이다. 진짜 온라인(한 항씩, 미래 길이 모름)에는 van der Hoeven의 relaxed multiplication 이 있어 같은 \(O(n \log^2 n)\)를 준다. 블록 크기를 2의 거듭제곱으로 쪼개, \(f_n\)이 확정될 때마다 "그 항이 영향을 주는 미래 블록들"에 기여를 예약 적립한다. 경시에서는 보통 구현이 단순한 CDQ를 쓴다.
정리
- 자기 참조 합성곱 점화식 = 온라인 합성곱.
- 인과율을 지키며 FFT를 쓰려면 CDQ 분할정복.
- \(O(n \log^2 n)\). 다음 강의에서 구현.