https://www.acmicpc.net/problem/33628
학교 1년 선배 군대 2주 선배 jjaewon9가 추천해줘서 풀었다. 추천해준 날짜가 5/5였으니 대강 2주 좀 안 걸린 것 같다. 지난 한 주 동안 야간 초소 경계근무를 섰는데, 마지막 근무 때 풀이 내고 오늘 짰다.
문제를 요약하면, $N$개의 정점을 트리로 합쳐가는 중에 입력받은 임의의 두 정점 사이의 경로에서 해당 컴포넌트의 최소 번호 정점과 가장 가까운 정점을 구하는 쿼리를 온라인으로 처리하는 문제이다. 최소 번호 정점의 위치는 임의적이고, union-find를 진행하며 컴포넌트의 최소 번호 정점은 쉽게 관리할 수 있다. 따라서 임의의 세 정점이 주어질 때 교차점을 찾는 조금 더 일반적인 문제로 생각해도 무방하다.
트리가 모두 만들어진 상태에서 세 정점의 교차점을 찾는 문제는 lca 응용 문제로 잘 알려진 https://www.acmicpc.net/problem/17399 와 동일하며, 케이스워크 등 여러 풀이가 있지만 가장 간단한 방법은 lca(a, b), lca(b, c), lca(c, a) 중 가장 깊이가 낮은 정점을 고르는 것이다. lca를 구하기 위해서는 조상을 관리하는 희소 배열이 필요하니, 트리를 합치는 과정에서 희소 배열을 동적으로 만들어갈 수 있다면 문제를 풀 수 있다.
트리에서 주로 사용되는 알고리즘 중 온라인으로 적용시킬 수 있는 방법은 내가 아는 한 small to large뿐이니 이쪽으로 생각해 보자. 두 컴포넌트를 잇는 간선을 추가할 때 부모 정점과 같은 컴포넌트였던 정점들의 조상은 변하지 않으니, 자식 정점이 속한 컴포넌트의 조상만 업데이트해 주면 된다. 정점이 $K$개인 컴포넌트의 조상 배열을 일일이 변경하는 시간복잡도는 $O(K\log N)$이므로, small to large의 논리에 따라 간선을 추가할 때마다 작은 컴포넌트를 자식으로 넣어 주면 $O(N\log^2N)$에 문제를 풀 수 있다.
희소 배열이 꼭 필요하다는 사고 없이 small to large만 가지고 생각하느라 한참 삽질했다
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
#include <bits/stdc++.h>
using namespace std;
int p[18][101010], par[101010], sz[101010], mi[101010], d[101010], N, Q, ans;
int find(int v){
return par[v] ? par[v] = find(par[v]) : v;
}
void merge(int a, int b){
a = find(a), b = find(b);
if (a == b) return;
if (sz[a] > sz[b]) swap(a, b);
par[a] = b;
sz[b] += sz[a];
mi[b] = min(mi[a], mi[b]);
}
vector<int> tree[101010];
void dfs(int v, int bef = -1){
for (int i = 1; i < 18; i++)
p[i][v] = p[i-1][p[i-1][v]];
for (int i : tree[v]){
if (i == bef) continue;
p[0][i] = v;
d[i] = d[v] + 1;
dfs(i, v);
}
}
pair<int,int> lca(int a, int b){
if (d[a] < d[b]) swap(a, b);
for (int i = 0; d[a] != d[b]; i++){
if ((d[a] - d[b]) & (1 << i))
a = p[i][a];
}
if (a == b) return {d[a], a};
for (int i = 17; i >= 0; i--){
if (p[i][a] != p[i][b])
a = p[i][a], b = p[i][b];
}
return {d[a]-1, p[0][a]};
}
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin >> N >> Q;
for (int i = 1; i <= N; i++) mi[i] = i, sz[i] = 1;
while (Q--){
int op, a, b;
cin >> op >> a >> b;
a ^= ans;
b ^= ans;
if (op == 1){
int pa = find(a), pb = find(b);
if (sz[pa] > sz[pb]) swap(a, b);
merge(a, b);
p[0][a] = b;
tree[a].push_back(b);
tree[b].push_back(a);
d[a] = d[b] + 1;
dfs(a, b);
}
else{
if (find(a) != find(b)) ans = 0;
else{
int m = mi[find(a)];
ans = max({lca(a, b), lca(b, m), lca(m, a)}).second;
}
cout << ans << '\n';
}
}
}
|
cs |
'문제풀이 > 백준' 카테고리의 다른 글
| ALOHA 여름방학 고급반 1주차 (0) | 2025.07.08 |
|---|---|
| 백준 33722 [B] 이진 매칭 (0) | 2025.06.06 |
| 백준 25432 각성제 (1) | 2025.05.05 |
| 백준 16287 Parcel (0) | 2024.06.23 |
| 백준 10070 벽 (0) | 2024.06.23 |