알고리즘 풀이/백준

[백준 11437] LCA

mhko411 2021. 4. 6. 12:59
728x90

문제

N(2 ≤ N ≤ 50,000)개의 정점으로 이루어진 트리가 주어진다. 트리의 각 정점은 1번부터 N번까지 번호가 매겨져 있으며, 루트는 1번이다.

두 노드의 쌍 M(1 ≤ M ≤ 10,000)개가 주어졌을 때, 두 노드의 가장 가까운 공통 조상이 몇 번인지 출력한다.

 

입력

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

 

출력

M개의 줄에 차례대로 입력받은 두 정점의 가장 가까운 공통 조상을 출력한다.


접근

LCA는 두 개의 정점이 공통으로 갖는 조상을 찾는 것이다. 트리는 사이클이 없는 구조이기 때문에 최소 공통 조상을 찾는다면 두 개의 정점의 최단 거리를 구할 수 있게 된다.

 

LCA를 위한 알고리즘을 다른 사이트를 참고하면서 공부를 많이했는데 한 번에 쉽게 이해되지 않았다.

현재까지 이해한 것은 다음과 같다.

1. LCA를 위해서 세그먼트 트리와 DP를 적용시킬 수 있다. 그 중 DP에 대한 방법을 공부하였다.

2. 트리를 양방향으로 만들어준다. 

3. 각 정점의 깊이와 첫 번째 부모를 어떠한 리스트에 저장해둔다. 깊이를 구하는 것은 두 개의 정점이 깊이가 다를 때 깊이를 맞춰준 후에 동시에 위로 올라가면서 부모를 찾기위함이다. 또한 첫번째 부모를 찾는 것은 두 개의 정점이 동시에 거슬러올라갈 때 부모가 같은지 검사하기위해 사용한다.

4. 첫 번째 부모를 구했다면 이를 활용하여 첫 번째 부모의 부모, 부모의 부모의 부모를 통해 루트까지의 부모를 구하고 저장한다.

5. 이제 깊이가 다르다면 깊이를 맞춰주도록 한다. 

6. 이후 동시에 거슬러 올라가면서 같은 부모가 나왔을 때 최소 공통 조상으로 정한다.

 

구현

정점들의 깊이와 바로 위에있는 첫 번째 부모를 각각 depth와 parent라는 리스트에 저장한다.

def dfs(cur, d):
    depth[cur] = d
    visited[cur] = True

    for child in tree[cur]:
        if not visited[child]:
            parent[child][0] = cur
            dfs(child, d+1)

 

이제 루트까지 거슬러 올라가면서 부모들을 찾는다. 

현재 정점의 i번째 부모는 i-1번째 부모의 부모이다. 

def find_parent():
    for i in range(1, 21):
        for j in range(1, V+1):
            parent[j][i] = parent[parent[j][i-1]][i-1]

 

lca함수에서 최소 공통 조상을 찾는다.

먼저 깊이를 맞춰주도록 한다.

b와 a 정점의 높이가 0이 될 때까지 더 깊이있는 정점의 위치를 갱신시킨다.

 

이후 같은 높이에 있을 때 루트까지 거슬러 올라가면서 같은 부모를 찾도록 한다.

def lca(a, b):
    if depth[a] > depth[b]:
        a, b = b, a
    for i in range(20, -1, -1):
        if depth[b] - depth[a] >= (1<<i):
            b = parent[b][i]

    if a == b:
        return b

    for i in range(20, -1, -1):
        if parent[a][i] != parent[b][i]:
            a = parent[a][i]
            b = parent[b][i]

    return parent[a][0]

전체 코드

import sys
input = sys.stdin.readline
sys.setrecursionlimit(100000)
def dfs(cur, d):
    depth[cur] = d
    visited[cur] = True

    for child in tree[cur]:
        if not visited[child]:
            parent[child][0] = cur
            dfs(child, d+1)

def find_parent():
    for i in range(1, 21):
        for j in range(1, V+1):
            parent[j][i] = parent[parent[j][i-1]][i-1]

def lca(a, b):
    if depth[a] > depth[b]:
        a, b = b, a
    for i in range(20, -1, -1):
        if depth[b] - depth[a] >= (1<<i):
            b = parent[b][i]

    if a == b:
        return b

    for i in range(20, -1, -1):
        if parent[a][i] != parent[b][i]:
            a = parent[a][i]
            b = parent[b][i]

    return parent[a][0]

def traversal(node):
    global answer

    answer += 1
    for i in tree[node]:
        traversal(i)



V = int(input())
tree = [[] for _ in range(V+1)]
for _ in range(V-1):
    a, b = map(int, input().split())
    tree[a].append(b)
    tree[b].append(a)

depth = [0] * (V+1)
parent = [[0 for _ in range(21)] for _ in range(V+1)]
visited = [False] * (V+1)



dfs(1, 0)
find_parent()
M = int(input())
for _ in range(M):
    A, B = map(int, input().split())
    common_parent = lca(A, B)
    print(common_parent)

'알고리즘 풀이 > 백준' 카테고리의 다른 글

[백준 14719] 빗물  (0) 2021.04.07
[백준 1655] 가운데를 말해요  (0) 2021.04.07
[백준 1780] 종이의 개수  (0) 2021.04.05
[백준 11729] 하노이 탑 이동 순서  (0) 2021.04.05
[백준 9372] 상근이의 여행  (0) 2021.04.05