Skip to content

Commit d6b2f67

Browse files
authored
Merge pull request #820 from jjthis/jthis
주감동 8월 개인과제
2 parents cc62742 + de526e7 commit d6b2f67

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
---
2+
layout: post
3+
title: Link Cut Tree Subtree Query
4+
date: 2025-08-26 04:30
5+
author: jthis
6+
tags:
7+
- algorithm
8+
- data-structure
9+
---
10+
11+
안녕하세요, **jthis**입니다.
12+
13+
이 글에서는 **Link-Cut Tree**에서 **Subtree Query**를 효율적으로 수행하는 방법에 대해 소개하겠습니다. 편의상 코드는 **Splay Tree**를 기준으로 설명하겠습니다.
14+
15+
# Link Cut Tree
16+
17+
Link-Cut Tree는 **간선 추가 삭제****경로 쿼리(Path Query)** 를 지원하는 자료구조입니다. 시간복잡도는 *amortized* $O(\log N)$이며, **amortized를 제거하여** $O(\log N)$으로도 가능합니다. 또한, 이 자료구조는 **Subtree 연산**도 지원할 수 있습니다. 따라서 Top Tree로 해결할 수 있는 대부분의 쿼리 문제를 거의 모두 **Link-Cut Tree**로 대체할 수 있습니다.
18+
19+
Link-Cut Tree는 Splay Tree로만 구현할 수 있다고 생각할 수도 있지만, 다른 BBST(Balanced Binary Search Tree)로도 충분히 구현할 수 있습니다. BBST에서 **split****merge** 연산만 빠르게 가능하다면, 어떤 BBST라도 Link-Cut Tree를 구성할 수 있습니다.
20+
21+
Link-Cut Tree는 트리를 **경로(Path)** 단위로 여러 개로 쪼개어 관리하는 자료구조입니다. 각 경로는 BBST로 관리하며, 경로에 포함되지 않은 간선은 **가상 간선**으로 생각하여 BBST와는 별도로 관리합니다.
22+
Link-Cut Tree에서는 **access 연산****makeRoot 연산**만 구현하면 됩니다.
23+
24+
**access 연산**은 트리 상의 루트와 특정 노드 $x$를 하나의 경로로 만드는 연산입니다. 이 연산을 통해 루트와 $x$ 사이에 있는 가상 간선을 실제 간선으로 바꾸고, 그 간선에서 부모 쪽과 연결되어 있던 자식과의 연결을 가상 간선으로 바꿉니다.
25+
26+
노드 $a$에서 access 연산을 수행한 뒤, 노드 $b$에서 access 연산을 수행했을 때, **마지막으로 바뀐 가상 간선, 즉 루트와 가장 가까운 가상 간선의 부모 쪽이 $a$와 $b$의 LCA**가 됩니다.
27+
28+
또한, access $x$ 연산을 수행한 뒤 해당 경로에 **reverse 연산**을 적용하면, 노드 $x$가 트리의 루트가 됩니다.
29+
30+
마지막으로, 간선을 추가하는 **Link** 연산을 위해서는 **자식 쪽을 트리에서 루트로 만들어야 합니다**.
31+
32+
```c++
33+
node *access(node *x) {
34+
splay(x);
35+
x->r = nullptr;
36+
node *res = x;
37+
while (x->p) {
38+
node *p = x->p;
39+
res = p;
40+
splay(p);
41+
p->r = x;
42+
splay(x);
43+
}
44+
return res;
45+
}
46+
47+
void makeRoot(node *x) {
48+
access(x);
49+
splay_reverse(x);
50+
}
51+
```
52+
53+
Link-Cut Tree는 간단히 말해 **BBST 사이의 가상 간선을 효율적으로 관리하는 자료구조**입니다.
54+
55+
# Link Cut Tree Path Query
56+
57+
두 노드 $a$와 $b$에서 **Path Query**를 수행하기 위해서는 *makeRoot* 연산 없이도 처리할 수 있지만, **makeRoot 연산을 사용하면 훨씬 간단하게 해결할 수 있습니다**.
58+
노드 $a$에서 makeRoot 연산을 수행한 뒤, 노드 $b$에서 access 연산을 실행하면, $a$-$b$ 경로만 BBST에 남게 되어 문제를 간단하게 해결할 수 있습니다.
59+
60+
```c++
61+
long long pathQuery(node *a, node *b) {
62+
makeRoot(a);
63+
access(b);
64+
return b->pathSum;
65+
}
66+
```
67+
# Link Cut Tree Subtree Query
68+
69+
**Subtree 연산**을 수행하기 위해서는 **가상 간선**을 적절히 관리하면 됩니다. 가상 간선은 **access 연산**에서만 변경됩니다.
70+
Subtree에서의 **min**, **max** 연산은 가상 간선을 BBST로 관리하면 해결할 수 있으며, **sum** 연산이 필요하다면 구조체에 `int` 하나를 추가하면 됩니다.
71+
또한, access 연산에서는 **min**, **max** 값은 삽입과 삭제(insert, delete)만 수행하면 되고, **sum**은 차이만 계산하여 누적하면 됩니다.
72+
73+
```c++
74+
void update(node *x) {
75+
x->mx = x->val;
76+
if (!x->vs.empty())x->mx = max(x->mx, *prev(x->vs.end()));
77+
if (x->l)x->mx = max(x->mx, x->l->mx);
78+
if (x->r)x->mx = max(x->mx, x->r->mx);
79+
}
80+
node *access(node *x) {
81+
splay(x);
82+
if (x->r)x->vs.insert(x->r->mx);
83+
x->r = nullptr;
84+
node *res = x;
85+
while (x->p) {
86+
node *p = x->p;
87+
res = p;
88+
splay(p);
89+
if (p->r)p->vs.insert(p->r->mx);
90+
p->vs.erase(p->vs.find(x->mx));
91+
p->r = x;
92+
splay(x);
93+
}
94+
return res;
95+
}
96+
```
97+
98+
**Subtree에서 최대값 연산**의 구현입니다. 구현의 편의를 위해 `multiset`을 사용했습니다.
99+
# Link Cut Tree Subtree Update
100+
### Subtree add
101+
```c++
102+
void rotate(node *x) {
103+
node *p = x->p;
104+
if (x == p->l) {
105+
p->l = x->r;
106+
if (p->l) {
107+
p->l->p = p;
108+
p->l->gets = p->added;
109+
}
110+
x->r = p;
111+
} else {
112+
p->r = x->l;
113+
if (p->r) {
114+
p->r->p = p;
115+
p->r->gets = p->added;
116+
}
117+
x->l = p;
118+
}
119+
x->p = p->p;
120+
if (x->p)x->gets = x->p->added;
121+
p->p = x;
122+
p->gets = x->added;
123+
if (x->p) {
124+
if (p == x->p->l)x->p->l = x;
125+
else if (p == x->p->r)x->p->r = x;
126+
}
127+
}
128+
129+
void adding(node *x, int diff) {
130+
x->sum += diff * x->sz;
131+
x->now += diff;
132+
x->added += diff;
133+
x->vsum += diff * x->vsz;
134+
}
135+
136+
void lazy_down(node *x) {
137+
if (x->p) {
138+
adding(x, x->p->added - x->gets);
139+
x->gets = x->p->added;
140+
}
141+
}
142+
```
143+
144+
`added``gets`라는 변수를 유지합니다.
145+
146+
* **added**: 해당 Subtree에 더해야 할 값의 누적 합
147+
* **gets**: 지금까지 부모로부터 받은 누적 합
148+
149+
`rotate` 함수는 부모와의 연결을 수정하기 때문에, 이 함수도 함께 수정해야 합니다. 또한 **Subtree 크기(size)** 도 유지해야 합니다.
150+
151+
---
152+
153+
### Subtree 변경 (Subtree Change)
154+
155+
`add` 연산과 유사하게 구현할 수 있습니다. 다만 값 자체를 저장하는 대신 **쿼리 인덱스**를 저장하여, 부모와 내가 다를 경우에만 업데이트하는 방식으로 처리합니다.
156+
157+
158+
# 예시 문제
159+
160+
### **1. [백준 14268 - 회사 문화 2](https://www.acmicpc.net/problem/14268)**
161+
162+
* **요구사항**: Subtree에 대한 `add` 업데이트와 특정 노드의 값 출력
163+
* **예시 코드**: [소스 링크](http://boj.kr/ef9fdd7e02954fedbd7c66c022d9c641)
164+
165+
---
166+
167+
### **2. [백준 13515 - 트리와 쿼리 6](https://www.acmicpc.net/problem/13515)**
168+
169+
* **요구사항**: 색깔(흰색/검정색)에 따라 연결 관리
170+
* **아이디어**:
171+
* **포레스트 2개**를 만든다 (0번 트리, 1번 트리)
172+
* 정점 `x`가 흰색이면 `x``x`의 부모는 **트리 0번**에서만 연결
173+
* 정점 `x`가 검정색이면 `x``x`의 부모는 **트리 1번**에서만 연결
174+
* **쿼리 1**:
175+
* 간선 추가 및 간선 삭제
176+
* **쿼리 2**:
177+
* u의 색깔에 맞는 트리에서 탐색
178+
* 두 가지 경우
179+
1. **트리 전체가 같은 색** → 제거 없이 `subtree size`
180+
2. **트리 중 루트만 색 다름** → 루트와의 간선 제거 후 `subtree size`
181+
* **예시 코드**: [소스 링크](https://www.acmicpc.net/source/share/a5dff557dde94d048b88e7e590bcce95)
182+
183+
---
184+
185+
### **3. [백준 13516 - 트리와 쿼리 7](https://www.acmicpc.net/problem/13516)**
186+
187+
* **요구사항**: Subtree에서 최대값 구하기
188+
* **아이디어**:
189+
* 위 문제(13515)와 거의 동일
190+
* 차이점 → `subtree size` 대신 `subtree max` 계산
191+
* **예시 코드**: [소스 링크](http://boj.kr/1576e635eac9445c9a6f6bed8f00647e)
192+
193+
---
194+
195+
### **4. [백준 18805 - Tree and Easy Queries](https://www.acmicpc.net/problem/18805)**
196+
197+
* **요구사항**: 지름(Diameter) 구하기
198+
* **아이디어**:
199+
* 동적 트리에서 지름 유지
200+
* **예시 코드**: [소스 링크](http://boj.kr/4a7037d27f994aafb2599fa91b494097)
201+
202+
---
203+
204+
### **5. [백준 10014 - Traveling Saga Problem](https://www.acmicpc.net/problem/10014)**
205+
206+
* **요구사항**: 지름(Diameter) 구하기
207+
* **아이디어**:
208+
* 위 문제(18805)와 동일한 방식
209+
* **예시 코드**: [소스 링크](http://boj.kr/6df8dbc3230c472ba3f733c621545e1d)
210+
211+
---
212+
213+
# Dynamic Tree 비교
214+
215+
| 구분 | Fragmented Tree | Euler Tour Tree | Link-Cut Tree | Top Tree |
216+
|---------------|-----------------|-----------------|--------------|-----------|
217+
| Path Query | 가능 | 제한적 | 가능 | 가능 |
218+
| Subtree Query | 가능 | 가능 | 가능 | 가능 |
219+
| 시간 복잡도 | O(√N) | O(log N) | O(log N) | O(log N) |
220+
| 구현 난이도 | 중간 | 비교적 쉬움 | 중간 | 어려움 |
221+
222+
# 응용
223+
224+
Link-Cut Tree는 **경로 쿼리(Path Query)****Subtree 쿼리**를 사용하여 다양하게 응용할 수 있습니다.
225+
* **트리 지름(Diameter)**
226+
* **Rerooting DP**
227+
* **Tree DP**
228+
* **Dynamic Centroid**
229+
* **Online LCA**
230+
* **Subtree Size 계산**
231+
* 기타 동적 트리 관련 쿼리
232+
233+
---
234+
235+
# 실전 문제
236+
237+
* [백준 17936 - 트리와 쿼리 13](https://www.acmicpc.net/problem/17936)
238+
* [백준 31705 - Kolorowy las](https://www.acmicpc.net/problem/31705)
239+
* [백준 1921 - 트리와 쿼리 20](https://www.acmicpc.net/problem/1921)
240+
* [백준 26408 - Game](https://www.acmicpc.net/problem/26408)
241+
* [Luogu P5610](https://www.luogu.com.cn/problem/P5610)
242+
* [Link-Cut Tree 문제집](https://www.acmicpc.net/workbook/view/7004)

0 commit comments

Comments
 (0)