[JAVA/백준] 11438번: LCA 2

👀 문제

https://www.acmicpc.net/problem/11438

👊 도전

1. 설계

  1. 최소 시간을 구해야 하므로 BFS를 이용한다.
  2. time[i]를 이용하여 위치 i까지 걸린 시간을 저장하고, 중복 방문을 피한다.
  3. 이동 경로를 출력해야 하므로 prev[i]를 통해 i의 이전 위치를 저장한다. Stack을 이용하여 이동 경로를 출력한다.

2. 구현 (성공 코드)

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
74
75
76
77
78
79
80
81
82
83
84
import java.util.*;
import java.io.*;
/**
 * @author HEESOO
 *
 */
class Main {
	static ArrayList<ArrayList<Integer>> list;
	static int[] depth;
	static int[][] parent;
	public static void main(String[] args) throws IOException {
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		int n=Integer.parseInt(br.readLine());
		int maxDepth=0;
		int check=1;
		while(check<=n) { // 트리의 깊이 계산
			check<<=1;
			maxDepth++;
		}
		
		list=new ArrayList<>();
		for(int i=0;i<n+1;i++) list.add(new ArrayList<>());
		depth=new int[n+1];
		parent=new int[n+1][maxDepth+1];
		
		for(int i=0;i<n-1;i++) {
			String[] s=br.readLine().split(" ");
			int a=Integer.parseInt(s[0]);
			int b=Integer.parseInt(s[1]);
			list.get(a).add(b);
			list.get(b).add(a);
		}
		dfs(1, 1); // 트리 생성
		setParent(n, maxDepth); // 부모 계산
		
		int m=Integer.parseInt(br.readLine());
		for(int i=0;i<m;i++) {
			String[] s=br.readLine().split(" ");
			int a=Integer.parseInt(s[0]);
			int b=Integer.parseInt(s[1]);
			System.out.println(solve(a, b, maxDepth));
			
		}
	}
	
	
	public static void dfs(int node, int cnt) {
		depth[node]=cnt; // node의 깊이 저장
		for(int n:list.get(node)) { // node와 연결된 곳 중
			if(depth[n]!=0) continue; // 방문한 곳은 패스
			parent[n][0]=node; // n의 첫 번째 부모는 node
			dfs(n, cnt+1); // n으로 이동
		}
	}
	
	public static void setParent(int n, int maxDepth) {
		for(int i=1;i<maxDepth;i++) { // 2^i번째 부모 저장
			for(int j=1;j<=n;j++) {
				parent[j][i]=parent[parent[j][i-1]][i-1];
			}
		}
	}
	
	public static int solve(int a, int b, int maxDepth) {
		if(depth[a]>depth[b]) { // a의 깊이가 b보다 더 작도록
			int temp=a;
			a=b;
			b=temp;
		}
		for(int i=maxDepth-1;i>=0;i--) { // a, b가 같은 깊이가 되도록 설정
			if(depth[a]<=depth[parent[b][i]]) b=parent[b][i];
		}
		
		if(a==b) return a;
		// 다르다면
		for(int i=maxDepth-1;i>=0;i--) { // 같이 깊이를 올리며 
			if(parent[a][i]!=parent[b][i]) {
				a=parent[a][i];
				b=parent[b][i];
			}
		}
		return parent[a][0];
	}
}

3. 결과

실행결과 🤟 성공 🤟
처음에는 트리를 ArrayList가 아니라 HashMap으로 구현하여 시간을 단축하고자 했는데 실패했다. 그래서 다른 사람들의 코드를 참고하였다. for문에서 maxDepth만큼 돌릴 때, maxDepth는 말 그대로 트리의 마지막 깊이이기 때문에 이 곳에서는 a, b의 공통 부모가 있을 수 없다. 그래서 maxDepth는 체크하면 안되고, maxDepth-1부터 시작해야한다.

4. 설명

  1. 시간을 줄이기 위해 node의 parent를 이차원 배열로 만든다
    • LCA (https://www.acmicpc.net/problem/11438)와 문제는 같으나, N의 개수가 2배이고, 시간 제한은 절반으로 줄었다.
    • 이전 문제에서는 parent[]를 일차원으로 만들어 i의 바로 위 부모만 저장해두고 역이동으로 최소 공통 부모를 찾았다.
    • 이번에는 이차원 배열 parent[i][j]를 두고, i의 2^j번째 부모들을 저장한다. i의 홀수 번째 부모는 배열에 저장되지 않아 모든 부모를 탐색하지 못한다고 생각할 수도 있다. 하지만 parent[i][1]를 k라고 할 때, i의 3번째 부모는 parent[k][0]으로 방문 가능하므로 결국 모든 부모들을 탐색할 수 있다.
  2. 트리의 최대 깊이를 계산한다
    • 노드가 n개인 균형이진트리의 깊이는 logn을 내림한 값과 같다(근데 문제에서 트리가 균형이진트리라는 말이 없는데.. 이 부분은 잘 이해가 안간다).
    • 자바에는 밑이 2인 로그를 계산하는 함수가 따로 없기 때문에, 비트 연산을 통해 최대 깊이(maxDepth)를 구한다.
    • 왼쪽으로 쉬프트하는 것은 2를 곱하는 것과 같으므로 해당 방법으로 maxDepth를 구한다.
  3. dfs()로 트리를 만든다
    • 이 코드는 이전 문제 같으므로 자세한 설명은 생략한다.
    • for문에서 n의 바로 위 부모는 2^0번째이므로 parent[n][0]에 node를 저장한다. 나머지 부모들은 setParent()에서 구한다.
  4. setParent()로 부모들을 저장한다
    • i는 깊이, j는 노드이다. 이 순서를 바꿀 경우 노드 j에 따른 2^i번째 부모들을 한 번에 구하게 되는데, 이 경우 아직 값이 저장되지 않은 부모를 참조하여 0이 들어가게 되므로 안된다.
    • parent[j][i]는 j의 2^i번째 부모와 같다. 이는 j의 바로 앞 부모 노드인 parent[j][i-1]의 2^(i-1) 번째 부모를 구하는 것과 같다.
  5. solve()로 LCA를 구한다
    • 트리에서 a가 b보다 위에 있도록 설정한다.
    • b를 움직여 a와 같은 깊이가 되도록 한다.
    • a==b라면 공통 부모를 가리키고 있는 것이므로 a(또는 b)를 리턴한다.
    • 아니라면, 같이 움직이며 공통 부모를 찾은 후 리턴한다.

👏 해결 완료!