어떤 문제를 푸는가
트리 구조 위에서 정의된 최적화/카운팅 문제를, 각 서브트리의 답을 자식에서
부모로 모아 올리며 계산하는 기법입니다. 트리는 사이클이 없어 부모-자식
관계가 명확하므로, "이 정점을 루트로 하는 서브트리의 답"이 자연스러운 상태가
됩니다.
대표 예: 트리의 최대 독립 집합, 서브트리 크기/합, 트리 지름, 각 정점을 루트로
한 경우의 선택 비용 등.
상태 설계의 출발점
루트를 하나 정하면 모든 정점이 부모와 자식을 갖습니다. 보통 상태는
$$ dp[v][\dots] = v \text{를 루트로 하는 서브트리에서의 (조건별) 최적값} $$
으로 둡니다. "\(v\)를 선택했는가" 같은 조건이 추가 차원으로 붙습니다.
예시 — 트리의 최대 독립 집합
인접한 두 정점을 동시에 고를 수 없을 때, 가장 큰(또는 가중치 합이 큰) 정점
집합을 찾습니다. 정점 \(v\)에 대해 두 상태를 둡니다.
dp[v][0]— \(v\)를 안 고를 때 서브트리의 최댓값.dp[v][1]— \(v\)를 고를 때 서브트리의 최댓값.
자식 \(c\)들에 대해:
$$ dp[v][0] = \sum_{c} \max(dp[c][0],\ dp[c][1]) $$
$$ dp[v][1] = w_v + \sum_{c} dp[c][0] $$
\(v\)를 고르면 자식은 못 고르고(\(dp[c][0]\)), \(v\)를 안 고르면 자식은 자유
(\(\max\))입니다. 답은 루트에서 \(\max(dp[root][0], dp[root][1])\).
왜 옳은가
서로 다른 서브트리는 부모를 통해서만 연결되므로, 한 자식의 선택은 다른 자식의
선택에 직접 영향을 주지 않습니다. 부모의 상태(고름/안 고름)만 고정하면 각
자식 서브트리는 독립적으로 최적화됩니다. 이 독립성이 "자식의 답을 합쳐
부모의 답을 만든다"는 트리 DP의 정당성을 줍니다.
복잡도
각 정점과 간선을 상수 번 처리하므로
$$ O(V) $$
(상태 차원이 작은 상수일 때). 상태가 추가 차원 \(K\)를 가지면 \(O(V \cdot K)\)
또는 자식 결합에 \(O(V \cdot K^2)\)가 되기도 합니다.
구현 형태
후위 순회(post-order)로 자식을 먼저 계산 한 뒤 부모를 계산합니다. DFS
재귀가 가장 자연스럽고, 정점이 많으면 스택 오버플로를 피해 명시적 스택이나
BFS 역순(위상 순서)으로 처리합니다. 다음 강의에서 구현과 리루팅(rerooting)을
봅니다.