본문으로 건너뛰기
김신건의 로그

K번째 조상 (K-th Ancestor)

· 수정 · 📖 약 4분 · 1,225자/단어 #algorithm #tree #binary-lifting #lca #ancestor
Kth Ancestor, K-th Ancestor, K번째 조상, kth ancestor, K-th parent, level ancestor

정의

K번째 조상 문제 (K-th ancestor) 는 루트 트리에서 정점 의 조상 중 로부터 정확히 만큼 위에 있는 정점 (즉, 번째 부모) 을 찾는 문제입니다.

정의:

\text\{kthAncestor\}(u, k) = \begin\{cases\} u & (k = 0) \\ \text\{parent\}(u) & (k = 1) \\ \text\{kthAncestor\}(\text\{parent\}(u), k - 1) & (k \geq 2) \end\{cases\}

의 깊이보다 크면 조상이 없음 (일반적으로 반환).

왜 어려운가

Naive: 부모를 한 번씩 따라 올라감. O(k) per query. 이면 총 O(NQ) = 로 안 됨.

핵심 트릭은 Binary Lifting 으로 번째 조상 테이블을 O(N log N) 시간에 미리 계산하고, k 를 이진 표현으로 분해하여 O(log N) 안에 답합니다.

알고리즘: Binary Lifting

아이디어

를 이진법으로 분해합니다.

각 비트 만큼 위로 올라가는 것을 미리 저장 해두면, set bits 만 순회하며 조상을 찾을 수 있습니다.

:

에서 시작해:

  1. 번째 조상으로 이동
  2. 번째 조상으로 이동
  3. 번째 조상으로 이동

총 3번 점프로 13번째 조상 도달.

왜 이게 가능한가

관건은 번째 조상 = 번째 조상의 번째 조상 이라는 재귀 구조.

이 함성 관계로 전처리.

자세한 시각화는 Binary Lifting 참조.

시각화: k = 13 조상 찾기

flowchart LR
  U["u<br/>depth=20"] -->|"2^0 = 1 step"| A1["depth=19"]
  A1 -->|"2^2 = 4 steps"| A2["depth=15"]
  A2 -->|"2^3 = 8 steps"| A3["depth=7<br/>= 13번째 조상"]

  style U fill:#fef3c7
  style A3 fill:#a7f3d0

한 번의 O(log k) 순회로 13 = 1 + 4 + 8 을 조합.

알고리즘 (의사코드)

전처리 (트리 DFS):
  up[u][0] = parent[u]
  for i in 1..LOG:
    up[u][i] = up[up[u][i-1]][i-1]

쿼리 kthAncestor(u, k):
  for i in 0..LOG:
    if k & (1 << i):
      u = up[u][i]
      if u == -1: return -1
  return u

LOG = ceil(log2(N)). 이면 LOG = 17 로 충분.

구현

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 500005;
const int LOG = 20;
int up[MAXN][LOG];
int depth[MAXN];
vector<int> adj[MAXN];

void dfs(int u, int p) {
  up[u][0] = p;
  for (int i = 1; i < LOG; i++) {
      up[u][i] = up[u][i-1] < 0 ? -1 : up[up[u][i-1]][i-1];
  }
  for (int v : adj[u]) if (v != p) {
      depth[v] = depth[u] + 1;
      dfs(v, u);
  }
}

int kthAncestor(int u, int k) {
  for (int i = 0; i < LOG; i++) {
      if (k & (1 << i)) {
          u = up[u][i];
          if (u < 0) return -1;
      }
  }
  return u;
}

int main() {
  int n, q;
  cin >> n;
  for (int i = 0; i < n - 1; i++) {
      int a, b;
      cin >> a >> b;
      a--; b--;
      adj[a].push_back(b);
      adj[b].push_back(a);
  }
  depth[0] = 0;
  dfs(0, -1);
  cin >> q;
  while (q--) {
      int u, k;
      cin >> u >> k;
      u--;
      int ans = kthAncestor(u, k);
      cout << (ans < 0 ? -1 : ans + 1) << "\n";
  }
}
stdin
5
1 2
1 3
2 4
2 5
3
5 2
4 1
5 4
결과
1
2
-1

복잡도

항목
전처리O(N log N) 시간, O(N log N) 공간
쿼리O(log N)
전체O((N + Q) log N)

응용

1. LCA (Lowest Common Ancestor)

LCA 를 구할 때:

  1. 깊이 차이만큼 낮은 쪽을 K번째 조상으로 올림 ().
  2. 남은 부분은 두 정점 동시에 위로 (top-down doubling).

K번째 조상은 LCA 의 핵심 부품.

2. Path sum on tree

에서 번째 조상까지의 경로 위 값 합계. 방문 순서에 따른 부분합 + K번째 조상 위치로 계산.

자세한 것은 Path sum on tree 참조.

3. Level Ancestor (LA)

일반화 문제. 에서 특정 깊이 의 조상 = .

4. Functional Graph K-th successor

트리 대신 각 정점이 하나의 다음 정점을 가리키는 그래프에서 번째 후속자.

같은 doubling 기법. 예: BOJ 17435 합성함수와 쿼리.

5. 문자열 서픽스 링크 (Suffix Automaton)

Suffix automaton 의 link 그래프도 트리 형태. 특정 상태의 번째 조상 상태.

함정

1. LOG 계산

LOG = ceil(log2(N)) + 1 로 여유. 이면 LOG >= 20.

2. Off-by-one

  • kth(u, 0) = u 자신 (조상 아님)
  • kth(u, 1) = 부모
  • = -1

3. 재귀 스택 오버플로

이면 Python / C++ 모두 스택 부족 가능. BFS 방식 iterative DFS 사용.

4. Doubling 채우기 순서

up[u][i] = up[up[u][i-1]][i-1] 를 계산할 때 up[?][i-1] 이 이미 채워져 있어야 함. DFS 방문 순서로 채우면 부모가 먼저 처리되므로 안전.

BOJ 연습 문제

번호제목링크
BOJ 3653영화 수집BOJ
BOJ 17435합성함수와 쿼리BOJ
BOJ 3176도로 네트워크BOJ
BOJ 13511트리와 쿼리 2BOJ

참고

이 글의 용어 (5개)
오일러 투어 테크닉 (Euler Tour Technique)algorithm
정의 오일러 투어 테크닉 (ETT, Euler Tour Technique) 은 트리를 DFS 방문 순서로 펼쳐 서브트리 쿼리를 구간 쿼리로 변환하는 정형. 각 노드 u 의 in-…
최소 공통 조상 (Lowest Common Ancestor)algorithm
정의 최소 공통 조상 (LCA, Lowest Common Ancestor) 는 트리에서 두 노드 u, v 의 공통 조상 중 가장 깊은 (루트에서 가장 먼) 노드. Binary L…
트리 경로 합 (Path Sum on Tree)algorithm
정의 트리 경로 합 (Path Sum on Tree) 는 트리에서 두 정점 $u, v$ 사이의 유일 경로 위 정점 (또는 간선) 값의 합 을 구하는 문제입니다. 문제 상황은 크게…
희소 배열 (Sparse Table)algorithm
정의 희소 배열 (Sparse Table) 은 정적 배열에서 결합 법칙을 만족하는 idempotent 연산 (min, max, gcd, lcm 등) 의 구간 쿼리를 O(1) 시간…
Binary Lifting: 2의 지수 doublingalgorithm
정의 Binary Lifting (이진 상승, doubling) 은 트리 (또는 functional graph) 에서 정점의 $2^k$ 번째 조상 을 미리 계산해 두어 임의의 $…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기