👀 문제
https://www.acmicpc.net/problem/11438
👊 도전
1. 설계
- 최소 시간을 구해야 하므로 BFS를 이용한다.
- time[i]를 이용하여 위치 i까지 걸린 시간을 저장하고, 중복 방문을 피한다.
- 이동 경로를 출력해야 하므로 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. 설명
- 시간을 줄이기 위해 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]으로 방문 가능하므로 결국 모든 부모들을 탐색할 수 있다.
- 트리의 최대 깊이를 계산한다
- 노드가 n개인 균형이진트리의 깊이는 logn을 내림한 값과 같다(근데 문제에서 트리가 균형이진트리라는 말이 없는데.. 이 부분은 잘 이해가 안간다).
- 자바에는 밑이 2인 로그를 계산하는 함수가 따로 없기 때문에, 비트 연산을 통해 최대 깊이(maxDepth)를 구한다.
- 왼쪽으로 쉬프트하는 것은 2를 곱하는 것과 같으므로 해당 방법으로 maxDepth를 구한다.
- dfs()로 트리를 만든다
- 이 코드는 이전 문제 같으므로 자세한 설명은 생략한다.
- for문에서 n의 바로 위 부모는 2^0번째이므로 parent[n][0]에 node를 저장한다. 나머지 부모들은 setParent()에서 구한다.
- setParent()로 부모들을 저장한다
- i는 깊이, j는 노드이다. 이 순서를 바꿀 경우 노드 j에 따른 2^i번째 부모들을 한 번에 구하게 되는데, 이 경우 아직 값이 저장되지 않은 부모를 참조하여 0이 들어가게 되므로 안된다.
- parent[j][i]는 j의 2^i번째 부모와 같다. 이는 j의 바로 앞 부모 노드인 parent[j][i-1]의 2^(i-1) 번째 부모를 구하는 것과 같다.
- solve()로 LCA를 구한다
- 트리에서 a가 b보다 위에 있도록 설정한다.
- b를 움직여 a와 같은 깊이가 되도록 한다.
- a==b라면 공통 부모를 가리키고 있는 것이므로 a(또는 b)를 리턴한다.
- 아니라면, 같이 움직이며 공통 부모를 찾은 후 리턴한다.
👏 해결 완료!
- 백준 11438 Java https://velog.io/@imfksh/%EB%B0%B1%EC%A4%80-11438-Java
- LCA(Lowest Common Ancestor) 알고리즘https://www.crocus.co.kr/660