본문 바로가기

알고리즘

BOJ 11437 LCA

www.acmicpc.net/problem/11437

 

11437번: LCA

첫째 줄에 노드의 개수 N이 주어지고, 다음 N-1개 줄에는 트리 상에서 연결된 두 정점이 주어진다. 그 다음 줄에는 가장 가까운 공통 조상을 알고싶은 쌍의 개수 M이 주어지고, 다음 M개 줄에는 정

www.acmicpc.net

LCA 문제이다. (Lowest Common Ancestor )

 

사실 이 문제에 대한 최적화 방안은 한창 알고리즘을 공부할 때 같이 스터디를 하는 형한테서 들었지만, 따로 구현은 해보지 않았었다. 

처음 이 문제를 접한다면 다들 똑같은 생각을 할 것이다. (물론 나도 처음에는 같은 생각을 했었다.)

"시작 node들로부터 depth가 같은 ancestor 까지 탐색을 진행 한 뒤, 방금 구한 ancestor들 부터 같은 node가 나올 때 까지 탐색을 진행한다"

 

하지만, 이 문제를 까다롭게 하는 것은 Query의 수이다.

위의 생각대로 구현을 한다면 시간복잡도  O(n) 이 걸리고, Query 수가 총 m개이므로 해당 문제의 시간복잡도는 O(nm) 이 된다.

 

이 상황을 해결할 수 있는 것은 탐색시간을 줄이는 것으로,

첫째, 한칸 한칸씩 올라가며 탐색하는 것이 아니라, 처음에는 1칸, 다음에는 2칸, .... , n 번째에는 2^n 칸을 탐색하는 것으로 시간을 O(n) 에서 O(log n) 으로 줄일 수 있다.

둘째, 이를 사용하기 위해서는 2^n 번째 조상이 저장되어 있는 배열이 하나 필요하다.

ancestor [node][n]가 node의  2^n번째 조상을 저장하고 있다고 할 때

해당 배열의 점화식은 다음과 같다.

ancestor [node][n] = ancestor[ ancestor[ node ][ n-1 ] ][ n-1 ]   

점화식의 의미는 "2^n 번째 조상 node는  2^(n-1) 번째 조상 node의  2^(n-1) 번째 조상 node와 같다." 이다.

 

자세한 설명이 필요하다면 다른 갓갓 분들이 쓰신 블로그를 참조하거나 추후 올릴 포스트를 참조하시면 될 것 같다.

 

 

첫번째 사항과 두번째 사항을 생각하며 구현한다면 다음과 비슷하게 나올 것이다. (아래 코드는 내가 제출한 코드이다.)

 

#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;

int n, m;
int ancestor[50001][30];
int depth[50001];
vector<vector<int>> graph;

int lca(int a, int b){
    if(depth[a] < depth[b])
        swap(a,b);
    int sub = depth[a] - depth[b];
    int power = 0;

    while(sub){
        int t = sub%2;
        sub /= 2;
        if(t != 0)
            a = ancestor[a][power];
        power += 1;
        //안올라가는줄 알았는데 왜 올라가야하지
    }
    while(a != b){
        power = 0;
        while(ancestor[a][power] != ancestor[b][power]){
            power += 1;
        }
        if(power > 0)
            power -= 1;
        a = ancestor[a][power];
        b = ancestor[b][power];
    }
    return a;
}

void make_ancestor(){

    //BFS로 짰는데, DFS로 짜면 더 짧아질 것 같음
    bool visited[50001] = {false,};
    int d = 0;
    queue<int> q;
    q.push(1);
    depth[1] = 0;
    
    while(q.size()){
        int qsize = q.size();
        for(int i=0;i<qsize;i++){
            int now = q.front();
            visited[now] = true;
            q.pop();
            for(int next : graph[now]){
                if(!visited[next]){
                    ancestor[next][0] = now;
                    depth[next] = depth[now]+1;
                    q.push(next);
                }
            }
        }
    }

    for(int index = 1;index<30; index++){
        int t = 0;
        for(int i=2;i<=n;i++){
            ancestor[i][index] = ancestor[ancestor[i][index-1]][index-1];
            t += ancestor[i][index];
        }
        if(!t) 
            break;
    }
}


int main(){
    
    scanf("%d",&n);
    graph.resize(n+1);
    for(int i=0;i<n-1;i++){
        int a, b; scanf("%d%d",&a,&b);
        graph[a].push_back(b);
        graph[b].push_back(a);
    }

    make_ancestor();

    scanf("%d",&m);
    for(int i=0;i<m;i++){
        int a, b; scanf("%d%d",&a,&b);
        printf("%d\n", lca(a,b));
    }

    return 0;
}

'알고리즘' 카테고리의 다른 글

BOJ 7423 합이 0인 네 정수  (0) 2021.01.27
BOJ 2252 줄 세우기  (0) 2021.01.26
BOJ 4386 별자리 만들기  (0) 2021.01.23
BOJ 1806 부분합  (0) 2021.01.22
BOJ 2467 용액  (0) 2021.01.21