문제
간선이 추가(link)·삭제(cut)되는 동적 포레스트에서, 임의의 두 정점 경로에 대한 질의(합/최대/갱신)와 루트 변경, 연결성 판정을 모두 \(O(\log n)\) amortized에 처리한다. 정적 트리라면 HLD로 충분하지만, 구조 자체가 바뀌면 링크-컷 트리(LCT)가 필요하다.
preferred path 분해
각 정점은 자식들 중 최대 하나를 preferred child 로 지정한다. preferred 간선을 따라가면 트리가 여러 preferred path 로 분해된다. LCT는 각 preferred path를 하나의 스플레이 트리(보조 트리) 로 저장하되, 그 path 위 정점들을 깊이 순(중위 순서) 으로 담는다.
path-parent 포인터
서로 다른 preferred path를 잇기 위해, 한 보조 스플레이 트리의 루트는 path-parent 포인터로 자기 path의 최상단 정점의 실제 트리 부모를 가리킨다. 이 포인터는 스플레이 트리의 일반 부모 포인터와 구분되며, 회전에 영향받지 않는다(preferred child가 아님).
access 연산
LCT의 모든 것은 access(v) 위에 세워진다. access(v) 는 루트에서 \(v\) 까지의 경로를 하나의 preferred path로 만든다. 구현은 \(v\) 를 스플레이로 올린 뒤, path-parent를 따라 위로 올라가며 각 단계에서 preferred child를 교체하고 다시 스플레이한다. access 이후 \(v\) 가 속한 보조 트리가 곧 루트–\(v\) 경로 전체를 담으므로, 그 보조 트리의 집계값(합/최대)이 경로 질의 답이다.
주요 연산
| 연산 | 방법 |
|---|---|
access(v) |
루트–\(v\) 를 한 path로 |
makeRoot(v) |
access(v) 후 보조 트리 reverse 플래그 |
link(u,v) |
makeRoot(u); parent(u)=v |
cut(u,v) |
makeRoot(u); access(v); 좌측 자식 분리 |
| 경로 질의 | makeRoot(u); access(v); \(v\) 보조 트리 집계 |
amortized 분석
access의 preferred child 교체 총횟수는 light-edge 논변(heavy-light처럼)으로 한 연산당 amortized \(O(\log n)\) 이고, 스플레이 자체도 amortized \(O(\log n)\). 따라서 전체 \(O(\log n)\) per op. makeRoot의 reverse는 스플레이 트리의 구간 뒤집기 lazy로 처리한다(스플레이 단원과 동일).