문제


 

 

 

풀이


LCA(Lowest Common Acestor_최소 공통 조상) 알고리즘을 사용하는 기본 유형의 문제다.

해당 문제에서 주의깊게 봐둬야할 건 각 노드의 2^i 번 째 조상노드를 테이블에 저장하기 위해 DP를 사용한 점화식이다.

  1. DFS 탐색을 통해 각 노드별 depth를 구해 배열에 저장하고, 각 노드의 첫 번째 조상노드를 parent 배열에 담는다. (DP 사전준비)
  2. DP 알고리즘을 통해 각 노드의 모든 2^i 번 째 조상노드를 구해 parent 배열에 저장한다.
  3. LCA(a, b)에서 a와 b의 깊이를 맞춘 후, 차례로 2^i 번 째 조상노드를 구해가며 최소 공통 조상을 찾는다.

 

위의 2번을 보면 DP를 통해 각 노드의 2^i 번 째 조상노드를 저장한다고 되어 있는데, 해당 코드를 보자.

for (int i = 1; i < depth; i++) {
for (int j = 1; j <= N; j++) {
parent[j][i] = parent[ parent[j][i - 1] ][i - 1];
}
}

 

A 정점의 2^1 번 째 조상은 A 정점의 2^0 번 째 조상노드의 2^0 번 째 조상과 같다.

A 정점의 2^1 번 째 조상노드 = A 정점의 2^0 번 째 조상노드의 2^0 번 째 조상노드

 

 

 

코드


import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
public class Main {
private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
private static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
private static int N, M, K;
private static List<List<Integer>> tree;
private static int[] depth;
private static int[][] parents;
public static void main(String[] args) throws IOException {
StringTokenizer st;
N = Integer.parseInt(br.readLine());
tree = new ArrayList<>();
for (int i = 0; i < N + 1; i++)
tree.add(new ArrayList<>());
for (int i = 0; i < N - 1; i++) {
st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
tree.get(a).add(b);
tree.get(b).add(a);
}
int tmp = 1;
K = 0;
while (tmp <= N) { // 최대 depth 알아내기.
tmp <<= 1;
K++;
}
depth = new int[N + 1];
parents = new int[N + 1][K];
dfs(1, 1);
fillParents();
M = Integer.parseInt(br.readLine());
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
int lca = lca(a, b);
bw.write(lca + "\n");
}
bw.flush();
bw.close();
br.close();
}
private static int lca(int a, int b) {
if (depth[a] < depth[b]) { // 깊이가 낮은 쪽을 기준으로 맞춘다.
int temp = a;
a = b;
b = temp;
}
//더 깊은 a를 2승씩 점프하며 두 노드의 depth를 맞춘 후, 맞춘 depth의 조상 노드로 대체한다.
for (int i = K - 1; i >= 0; i--) {
if (Math.pow(2, i) <= depth[a] - depth[b]) {
a = parents[a][i]; // a를 2^i 번 째 조상 노드로 대체한다.
}
}
// depth 맞춘 후 대체한 조상 노드가 b와 같다면. 즉, a의 조상노드가 b라면 종료한다.
if (a == b) return a;
// 이제 두 노드는 같은 depth를 가지기 때문에
// 같이 2승씩 점프시키며 공통부모 바로 아래까지 올린다.
for (int i = K - 1; i >= 0; i--) {
if (parents[a][i] != parents[b][i]) {
a = parents[a][i];
b = parents[b][i];
}
}
return parents[a][0];
}
private static void fillParents() {
for (int i = 1; i < K; i++) { // DP를 이용해 각 노드별 2^K 번 째 조상 노드를 저장한다.
for (int j = 1; j <= N; j++) {
parents[j][i] = parents[ parents[j][i - 1] ][i - 1];
}
}
}
private static void dfs(int node, int cnt) {
depth[node] = cnt;
for (Integer next : tree.get(node)) {
if (depth[next] == 0) {
dfs(next, cnt + 1);
parents[next][0] = node;
}
}
}
}

댓글

댓글을 사용할 수 없습니다.