티스토리 뷰

정리

LCA

logwns 2021. 7. 1. 16:06

(이 내용은 제가 이해한 걸 정리해둔 거라 틀린 내용이 있을 수도 있습니다.)

 

LCALowest Common Ancestor로 트리에서 두 노드가 있을 때 두 노드의 최소 공통 조상을 찾는 알고리즘이다.

 

각 노드에 깊이가 있을 때(루트 노드=$1$, 루트 노드의 자식들=$2,3,...$)

(1) 두 노드의 깊이를 맞춰준 후

      $(depth_u=3, depth_v=5$ 이면 $\rightarrow$ $depth_u=depth_v=3)$

(2) $u=v$일 때까지 부모 노드로 계속 올라간다.

 

(1), (2)를 수행하는 과정에서 한쪽으로 치우쳐진 $n$개의 노드를 가진 트리이면 시간 복잡도가 $O(n)$이 된다.

LCA를 구하는 쿼리가 $m$개 들어오면 $O(nm)$이라서 문제를 풀 때 시간 초과가 되는데

이를 DP를 사용해서 $O(log(n))$만에 구할 수 있다.

 

한 단계씩 올라가면서 찾는 게 아니라 $2^x$씩 올라가면서 찾으면 한 쿼리당 시간 복잡도가 $O(log(n))$이 되고, $m$개의 쿼리를 처리하는데 $O(mlog(n))$의 시간이 된다. (+세그먼트 트리를 사용한 방법도 있는데 나중에 추가할 예정)

점화식을 $par_{x,i}$ : $x$노드의 $2^i (i\geq0)$번째 조상이라고 하면 $par_{x,i}=par_{par_{x,i-1},i-1}$ : $(x$의 $2^{i-1}$번째 조상$)$의 $2^{i-1}$의 조상이라 하면 된다.$(i\geq1)$

이런 방식으로 조상을 저장해두고 이를 참고해서 빠르게 올라가는 방법을 sparse table이라 한다.

 

(예시) $3, 6$노드의 LCA를 구하는 과정

 

(1) 두 노드의 깊이를 맞춰준다.

$depth_6=4$, $depth_3=2$, 깊이가 다르므로 $6$번 노드의 깊이가 $3$번 노드의 깊이와 같게 해준다. $6$번 노드에서 $par_{6,i}$ ($i : max\_height \rightarrow 0$)의 깊이를 보면서 $depth_x\geq depth_3$의 조건을 만족하는 $x$로 이동하고 이를 반복한다.

여기서 $max\_height$의 최대값은 $\lfloor log(n)\rfloor$이다. (한쪽으로 쏠린 편향 트리의 노드가 $n$일 때 바닥에서 $2^{\lfloor log(n)\rfloor}$만큼 올라가야 루트 노드가 되기 때문에)

이 작업을 $O(log(n))$에 할 수 있다.

 

 

(2) $u=v$일 때까지 부모 노드로 계속 올라간다.

두 노드의 높이가 같아 졌다면 (1)의 작업과 비슷하게 $x$($3$과 깊이가 같아진 노드, 위의 예제에선 $2$)와 $3$에 대하여  $par_{x,i} = par_{3,i}$, ($i : max\_height \rightarrow 0$)의 조건을 만족하는 노드로 이동하고 이를 반복하여 LCA를 구할 수 있다.

 

BOJ 11437 (LCA)

코드 :

<hide/>
#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;

const int N = 50000;
vector<int> adj[N + 2];
int height[N + 2];
int par[N + 2][16];
int max_height = 15;

void make_tree(int x, int parent) {

	height[x] = height[parent] + 1;
	par[x][0] = parent;

	for (int i = 1; i <= max_height; i++) {
		par[x][i] = par[par[x][i - 1]][i - 1];
	}
	for (auto it : adj[x]) {
		if (it == parent) continue;
		make_tree(it, x);
	}
	return;
}

int lca_query(int a, int b) {
	if (height[a] != height[b]) {
		if (height[a] > height[b]) swap(a, b);
		for (int i = max_height; i >= 0; i--) {
			if (height[a] <= height[par[b][i]]) b = par[b][i];
		}
	}
	int lca = a;
	if (a != b) {
		for (int i = max_height; i >= 0; i--) {
			if (par[a][i] != par[b][i]) {
				a = par[a][i];
				b = par[b][i];
			}
			lca = par[a][i];
		}
	}
	return lca;
}

int main() {
	int n, m;
	cin >> n;
	for (int i = 1; i < n; i++) {
		int x, y;
		cin >> x >> y;
		adj[x].push_back(y);
		adj[y].push_back(x);
	}

	make_tree(1, 0);

	for (cin >> m; m--;) {
		int x, y;
		cin >> x >> y;
		cout << lca_query(x, y) << '\n';
	}
	return 0;
}

 

(BOJ에서 11437, 11438, 1761, 3176, 13511을 풀면서 공부한 내용입니다.)

'정리' 카테고리의 다른 글

c++ priority_queue  (0) 2022.01.14
segment tree  (0) 2021.07.12
Heavy-Light Decomposition  (0) 2021.07.07
그리디 문제 풀기  (0) 2021.06.25
이항계수를 어떤수로 나눴을 때 값 구하기  (0) 2021.06.25
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/01   »
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
글 보관함