문제 설명
주어진 N개의 노드와 N-1개의 간선으로 이루어진 트리가 있습니다. 각 노드는 1부터 N까지의 정수로 번호가 매겨져 있습니다. 두 개의 노드 A와 B가 주어질 때, 이 두 노드의 최소 공통 조상(LCA, Lowest Common Ancestor)을 찾는 문제입니다. 단, 이 문제는 트리가 매우 클 수 있기 때문에, 효율적인 알고리즘을 이용해야 합니다.
입력
- 첫째 줄에 노드의 개수 N (1 ≤ N ≤ 100,000)이 주어진다.
- 둘째 줄에는 N-1개의 간선 정보가 주어진다. 각 간선은 “부모 자식”의 형태로 주어진다.
- 셋째 줄에는 LCA를 찾고자 하는 쿼리의 개수 Q (1 ≤ Q ≤ 100,000)와 각 쿼리에 대해 A, B가 주어진다.
예제 입력
7 1 2 1 3 2 4 2 5 3 6 3 7 4 5 3 4 5 4 6 5 6
예제 출력
2 1 3
해결 전략
최소 공통 조상을 효율적으로 찾기 위해서는 다음과 같은 접근 방식을 사용할 것입니다.
- 트리 구조의 표현: 트리는 인접 리스트를 사용하여 표현됩니다. 각 노드는 부모와 자식으로 연결되어 있으므로, 이를 바탕으로 트리를 구성합니다.
- DFS를 통한 깊이 배열과 부모 배열 생성: DFS(Depth First Search) 알고리즘을 사용하여 각 노드의 깊이를 구하고, 부모 노드를 기록합니다. 이 정보는 LCA를 찾는 데 필수적입니다.
- 이진 제안법: LCA를 효율적으로 찾기 위해 이진 제안법(Fast LCA)을 사용합니다. 두 노드의 깊이를 맞추고, 같은 조상을 찾는 방식으로 진행됩니다.
구현 단계
1. 트리 구조 만들기
먼저 간선 정보를 바탕으로 트리를 구축합니다. 이 과정에서는 부모와 자식 간의 관계를 정의하며, 이를 인접 리스트를 통해 표현합니다.
2. DFS로 깊이 및 부모 배열 생성하기
이제 DFS를 사용하여 각 노드의 깊이와 부모를 기록해야 합니다. 깊이는 각 노드가 루트에서 얼마나 떨어져 있는지를 나타내고, 부모는 다음 단계에서 LCA를 찾는 데 필수적입니다.
3. LCA 함수 구현하기
이진 제안법을 사용하여 두 노드의 최소 공통 조상을 찾는 함수를 구현합니다. 이 함수는 두 노드의 깊이를 비교하고, 필요에 따라 깊이를 맞춘 후 조상을 찾아가는 방식입니다.
4. 쿼리 처리하기
쿼리에서 주어진 두 노드 A와 B에 대해 LCA를 찾아 출력합니다.
코드 구현
#include#include #include #include using namespace std; const int MAX_N = 100000; const int LOG = 17; // log2(100000) ≈ 16.61 int parent[MAX_N + 1][LOG + 1]; // 부모 배열 int depth[MAX_N + 1]; // 깊이 배열 vector tree[MAX_N + 1]; // 인접 리스트 형식의 트리 int N; void dfs(int node, int par, int d) { depth[node] = d; parent[node][0] = par; for (int i = 1; i <= LOG; i++) { parent[node][i] = parent[parent[node][i - 1]][i - 1]; } for (int child : tree[node]) { if (child != par) { dfs(child, node, d + 1); } } } int lca(int a, int b) { if (depth[a] < depth[b]) swap(a, b); for (int i = LOG; i >= 0; i--) { if (depth[parent[a][i]] >= depth[b]) { a = parent[a][i]; } } if (a == b) return a; for (int i = LOG; i >= 0; i--) { if (parent[a][i] != parent[b][i]) { a = parent[a][i]; b = parent[b][i]; } } return parent[a][0]; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> N; for (int i = 0; i < N - 1; i++) { int u, v; cin >> u >> v; tree[u].push_back(v); tree[v].push_back(u); } dfs(1, 0, 0); // 루트는 1 int Q; cin >> Q; for (int i = 0; i < Q; i++) { int a, b; cin >> a >> b; cout << lca(a, b) << '\n'; } return 0; }
마무리
이 문제는 최소 공통 조상을 찾기 위해 DFS와 이진 제안법을 사용하는 방법을 잘 이해하는 데 도움을 줍니다. 트리를 처리하는 다양한 알고리즘을 익히고, 효율적인 코딩 능력을 기르는 데 큰 도움이 될 것입니다. 주어진 문제를 기반으로 다양한 방식으로 트리 구조를 탐색하고 LCA를 찾는 방법을 연습하면, 코딩테스트 준비에 매우 유용할 것입니다.