일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 하버-보슈법
- 알고리즘
- 백준
- 내란죄
- 내란수괴
- 다익스트라
- 비상계엄
- 유니온 파인드
- 이분 탐색
- ccw
- 6. 25. 전쟁
- DP
- union find
- 오블완
- 내란수괴 윤석열
- 투 포인터
- 프림
- dfs
- 구조론
- 티스토리챌린지
- 윤석열
- Python
- Prim
- 왈왈왈
- 분할정복
- LCA
- BFS
- 재귀함수
- 국민의 힘 뿌리
- dfs 백트래킹
- Today
- Total
Toolofv 님의 블로그
[Python] 백준 - 11438 LCA 2 본문
문제
N(2 ≤ N ≤ 100,000)개의 정점으로 이루어진 트리가 주어진다. 트리의 각 정점은 1번부터 N번까지 번호가 매겨져 있으며, 루트는 1번이다.
두 노드의 쌍 M(1 ≤ M ≤ 100,000)개가 주어졌을 때, 두 노드의 가장 가까운 공통 조상이 몇 번인지 출력한다.
입력
첫째 줄에 노드의 개수 N이 주어지고, 다음 N-1개 줄에는 트리 상에서 연결된 두 정점이 주어진다. 그 다음 줄에는 가장 가까운 공통 조상을 알고싶은 쌍의 개수 M이 주어지고, 다음 M개 줄에는 정점 쌍이 주어진다.
출력
M개의 줄에 차례대로 입력받은 두 정점의 가장 가까운 공통 조상을 출력한다.
문제해결방법 - (서칭 참조)
그 전의 빌드업 문제를 풀어보았어도, 희소배열을 적용하는 개념이 이해하기가 어려웠다. 아무 참조없이 이대로 풀 수 있다면 천재..;
필요한 도구
1) dp 2차원 배열 : log(정점의 최대갯수 100,000의 $2^{x}$의 x = log2(100000))을 행(row)으로, 정점의 갯수를 열(col)로 구성한다.
2) depth배열 : LCA알고리즘이 실행시, 깊이를 맞춰줄 때 활용된다. 최상위루트노드부터 깊이대로 저장된다.
3) dfs함수 : 초기 dp[0][1~n+1]의 배열을 저장하며, depth배열을 만들어준다.
4) v (방문리스트) : dfs함수 실행시, 방문한 노드를 체크하여 중복을 방지해준다.
문제해결방법
1. LCA알고리즘은 A) 먼저 깊이를 맞춰준 후, B) 부모노드가 맞아질 때까지 (x, y) 두 노드의 부모노드를 검색한다.
2. dp의 각 빈칸에는 $2^{i}$ 번째 부모노드가 저장되는 구조로 이뤄져 있다.
3. dp에 행인 log2(100000)+1은 2의 지수 범위를 반복문을 돌 때 필요하다.
4. $2^{i}$는 $2^{i-1}$ + $2^{i-1}$ 과 같다.
5. LCA함수 실행시 깊이가 맞춰졌다면, log2(100000)+1 부터 지수를 줄여가면서 같지 않아지는 시점에 x, y를 부모노드로 갱신하여 점차 좁아지는 검색범위에서 부모노드를 찾는다.
1) $2^{0}$ 까지 줄여도 같아지지않았다면 결국 루트노드는 공통되는 부모노드다.
2) 뭔가 그려놓고 따라가 보면 다 구해진다. 깊이가 큰 배열로 동작을 확인해보면 패턴을 더 정확하게 알 수 있을 것 같다.
3) 루트노드를 구하더라도, 혹시 그 밑의 공통조상이 있으면 그 노드로 선택될 수 있도록 작동된다.
4) 만약 큰 범위의 지수로 실행되었을 때, 부모노드가 근처의 몇 칸만 올라가도 된다면, 계속 부모노드가 if문에서 같다고 출력되어, 노드의 변화없이(큰 깊이의 노드를 유지한 채) 지수가 줄어들게 되고, 가장 가까운 공통 부모노드를 찾게될 것이다. (마치 지퍼 잠그듯이)
5) 큰 범위의 지수로 실행되었을 때, 예를들어 깊이가 $2^{16}$ 이고 부모노드가 중간에 위치한다면, 지수가 작아지면서 위의 부모노드는 계속 동일하다고 나오더라도, 분기되는 시점부터 $2^{4}$ , $2^{3}$ , $2^{2}$ 부분에서 값이 달라지니, 노드가 갱신되고, 지수 범위가 줄어들면서 부모노드를 찾게될 것이다. 부모노드가 그냥 바로 위에 존재한다면, $2^{0}$ 일 때, 부모노드를 찾게된다.
- 어차피 두 노드의 공통조상을 구하는 거기 때문에, 모형으로 보면 두 노드간의 족보만 보면 된다.
- 첫번째 그림은 중간과정부터라고 봐야한다. 계속 dp[i][a] != dp[i][b]라서 노드가 거슬러 올라가면서 갱신된다.
- dp[i][a] == dp[i][b]이면, 갱신되지 않고, 지수 i의 범위가 줄어든다.
- 코드
import sys
import math
sys.setrecursionlimit(10**6)
input = sys.stdin.readline
def dfs(idx, dep):
v[idx] = 1
depth[idx] = dep
for next in graph[idx]:
if v[next] == 0:
dp[0][next] = idx
dfs(next, dep+1)
def Makedp():
dfs(1, 0)
for r in range(1, log):
for c in range(1, n+1):
dp[r][c] = dp[r-1][dp[r-1][c]]
return
def LCA(a, b):
if depth[a] < depth[b]:
a, b = b, a
for i in range(log-1, -1, -1):
if (depth[a]-depth[b]) >= (1 << i):
a = dp[i][a]
if a == b:
return a
for i in range(log-1, -1, -1):
if dp[i][a] != dp[i][b]:
a = dp[i][a]
b = dp[i][b]
return dp[0][a]
log = int(math.log2(100000))+1
n = int(input())
graph = [[] for _ in range(n+1)]
depth = [0 for _ in range(n+1)]
v = [0 for _ in range(n+1)]
dp = [[0 for _ in range(n+1)] for _ in range(log)]
for _ in range(n-1):
a, b = map(int, input().split())
graph[a].append(b)
graph[b].append(a)
Makedp()
m = int(input())
for _ in range(m):
x, y = map(int, input().split())
print(LCA(x, y))
'Algorithm' 카테고리의 다른 글
[Python] 백준 - 11866 요세푸스 문제 0 (1) | 2024.09.08 |
---|---|
[Python] 백준 - 3176 도로 네트워크 (0) | 2024.09.03 |
[Python] 백준 - 3584 가장 가까운 공통 조상 (1) | 2024.09.02 |
[Python] 백준 - 1780 종이의 개수 (1) | 2024.09.01 |
[Python] 백준 - 1966 프린터 큐 (0) | 2024.08.25 |