SW Expert Academy
SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!
swexpertacademy.com
1. 문제의 유형 및 이해
BFS, LCA(최소 공통 조상)
최소 공통 조상(LCA, Lowest Common Ancestor)
최소 공통 조상(LCA, Lowest Common Ancestor) Ancestor Rooted Tree에서 root를 기준으로 높이가 정해집니다. root의 높이를 0이라고 둘 때 각각의 높이는 1씩 증가합니다. 이 때, 임의의 정점에서 '루트'로 향..
sogogi486.tistory.com
주어진 트리에서 BFS를 통해 그래프를 탐색하고자 합니다.
영준이는 직접 트리를 방문해야 하기 때문에 영준이는 정해진 BFS 순서대로 트리를 탐색하게 된다고 할 때,
영준이는 과연 몇 개의 간선을 지나고 나서야 탐색을 끝 마칠 수 있을까?
탐색을 할 때는 "작은 번호 부터" 순서대로 큐의 뒤쪽에 넣는 방식으로 탐색이 진행된다.
구하고자 하는 값은 결국 어떠한 노드와 노드 사이의 거리를 계산하는 문제입니다.
트리에서 노드와 노드 사이의 거리를 계산하기 위해서는 어떻게 해야할까요.
LCA를 활용하면 두 노드의 거리를 계산할 수 있습니다. LCA가 두 정점이 처음 만나는 지점이기 때문입니다.
2. 문제 접근 방법
BFS 탐색 순서로 진행된다고 하였기 때문에 BFS를 통해 탐색 순서를 받아옵니다.
탐색순서와 LCA를 활용하여 두 정점간의 거리를 계산 할 수 있습니다.
이 때, LCA를 구현하는 데에 조금만 더 생각해보도록 합시다.
문제에서의 노드 개수는 $10^5$ 개로 주어지고 있습니다.
일반적인 방법으로 LCA를 구하는 데에 O(N)의 시간복잡도를 가지게 되고,
노드 N개에 대해 N-1 번 반복하여 LCA를 구해야 합니다.
그래서 LCA를 O(N)에 구하게 된다면, O($N^2$) 시간복잡도를 가져 시간초과가 발생하게 됩니다.
그렇기 때문에 LCA를 조금 더 빠르게 구할 수 있는 "세그먼트 트리를 활용한 방법" 과 "dp를 활용한 방법" 중에서 dp를 활용한 방법을 사용하여 구현하도록 합니다.
dp를 활용할 경우에 전처리 시간에 O(NlogN) 그리고 각 질의에 응답하는 데에 O(logN)시간을 가지고 있습니다.
총 질의가 N-1개가 들어오기 때문에 전처리 시간과 질의에 응답하는 시간을 계산하면
O(NlogN)시간에 문제를 해결할 수 있게 됩니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;
/**
* 영준이의 real BFS
* bfs 순서대로 진행을 하면서 거리를 측정하여 이동거리를 계산한다.
* 이 때 어떠한 u에서 v로 갈 때는 항상 공통조상을 거쳐서 지나가게 된다.
* 그렇게 되면 u에서 공통조상까지의 거리 + v에서 공통조상까지의 거리가 더해지게 된다.
* 필요한건 현재와 이전값이 필요하다.
* Point는 어차피 BFS를 한번 해야합니다.
* 이 때 기록을 해 두고, 값 두개씩 가져와서 계산하는게 나을 듯합니다.
* root는 항상 1이고, 타색하는 노드는 작은 번호부터 탐색한다.
* */
public class Solution {
//LCA를 구해야합니다.
//parent가 정해져서 나온다.
static ArrayList<Integer>[] tree;
static int[] depth;
static int[][] parent;
static int maxLevel;
static int[] order;
static int N;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
StringBuilder sb = new StringBuilder();
int T = Integer.parseInt(br.readLine());
for (int tt = 1; tt <= T; tt++) {
sb.append('#').append(tt).append(' ');
N = Integer.parseInt(br.readLine());
maxLevel = calcMaxLevel(N) + 1;
depth = new int[N+1];
parent = new int[N + 1][maxLevel];
tree = new ArrayList[N+1];
order = new int[N];
for (int i = 0; i < N + 1; i++) tree[i] = new ArrayList<Integer>();
st = new StringTokenizer(br.readLine());
for(int i= 2;i<N+1;i++){
int p = Integer.parseInt(st.nextToken());
parent[i][0] = p;
tree[p].add(i);
}
//BFS with height And parent init
Queue<Integer> queue = new LinkedList<Integer>();
queue.add(1);
int idx =0 ;
while (!queue.isEmpty()) {
int cur = queue.poll();
order[idx++] = cur;
//parent 배열 계산
for(int i =1;i<maxLevel;i++){
parent[cur][i] = parent[parent[cur][i-1]][i-1];
}
for (int nxt : tree[cur]) {
depth[nxt] = depth[cur] +1;
queue.add(nxt);
}
}
//LCA 항상 i가 i+1보다 depth가 낮음. 항상 always
//구한 bfs순서대로 lca 질의를 통해 답을 알아내도록 합시다.
long ans =0;
for(int i =0;i<N-1;i++){
int lca = LCA(order[i], order[i + 1]);
ans += (depth[order[i]] - depth[lca]) + (depth[order[i + 1]] - depth[lca]);
}
sb.append(ans).append('\n');
}
System.out.print(sb);
}
public static int LCA(int a, int b){
//높이를 맞춰주어야겟죠
for(int i =maxLevel;i>=0;i--){
if(((depth[b] - depth[a])&1<<i)>0){
b = parent[b][i];
}
}
if(b==a) return a;
for(int i = maxLevel-1;i>=0;i--){
if (parent[a][i] != parent[b][i]) {
a = parent[a][i];
b = parent[b][i];
}
}
return parent[a][0];
}
public static int calcMaxLevel(int n) {
double log2 = Math.log(n)/Math.log(2);
return (int) Math.ceil(log2);
}
}
// 시간초과 LCA
// public static int solve(int a, int b) {
// int cnt =0;
// if(depth[a] > depth[b]){
// while(depth[a] != depth[b]){
// a = parent[a];
// cnt++;
// }
// }else if(depth[a] < depth[b]){
// while (depth[a] != depth[b]) {
// b = parent[b];
// cnt++;
// }
// }
// while(a != b){
// a = parent[a];
// b = parent[b];
// cnt += 2;
// }
// return cnt;
// }
3. Review
정점간의 거리를 어떻게 빨리 계산할 수 있는가 하는 문제였습니다.
공통 조상 즉, 처음으로 겹치는 부분을 찾아서 가면 되겠다라고 직관적으로 이해하기는 쉬우나
LCA 알고리즘을 몰랐더라면, 또는 알고있더라도 많은 질의에 응답할 수 있도록 최적화하지 않는다면
문제를 풀기에 꽤나 어려운 문제였을 것 같습니다.
'알고리즘 > 문제풀이' 카테고리의 다른 글
[백준] Q13460 구슬탈출2 JAVA (0) | 2022.02.11 |
---|---|
[백준] Q11049 행렬 곱셈 순서 (0) | 2022.02.11 |
[SWEA] 그래도 수명이 절반이 되어서는... (0) | 2022.02.09 |
[SWEA] 10507 영어공부 JAVA (0) | 2022.02.08 |
[SWEA] 최대상금 JAVA (0) | 2022.02.08 |