mojo's Blog
[백준 11812] K진 트리 본문
문제 링크 : 11812번: K진 트리 (acmicpc.net)
문제
각 노드가 자식을 최대 K개 가질 수 있는 트리를 K진 트리라고 한다. 총 N개의 노드로 이루어져 있는 K진 트리가 주어진다.
트리는 "적은 에너지" 방법을 이용해서 만든다. "적은 에너지" 방법이란, 이전 깊이를 모두 채운 경우에만, 새로운 깊이를 만드는 것이고, 이 새로운 깊이의 노드는 가장 왼쪽부터 차례대로 추가 한다.
아래 그림은 노드 9개로 이루어져 있는 3진 트리이다.
노드의 개수 N과 K가 주어졌을 때, 두 노드 x와 y 사이의 거리를 구하는 프로그램을 작성하시오.
입력
첫째 줄에 N (1 ≤ N ≤ \(10^{15}\))과 K (1 ≤ K ≤ 1 000), 그리고 거리를 구해야 하는 노드 쌍의 개수 Q (1 ≤ Q ≤ 100 000)가 주어진다.
다음 Q개 줄에는 거리를 구해야 하는 두 노드 x와 y가 주어진다. (1 ≤ x, y ≤ N, x ≠ y)
출력
총 Q개의 줄을 출력한다. 각 줄에는 입력으로 주어진 두 노드 사이의 거리를 출력한다.
최소 공통 조상을 이용한 문제이다.
이 문제의 핵심은 N 값이 굉장히 커서 배열을 이용한 풀이가 금지된다. (N이 최대 \(10^{15}\) 라는점)
※ 문제 접근 방법
① 부모와 자식이 어떠한 관계를 갖는지 규칙성을 찾아야 한다.
이때, 문제에서 "이 새로운 깊이의 노드는 가장 왼쪽부터 차례대로 추가 한다" 라는 조건을 활용한다면 부모와 자식이 어떠한 관계를 갖는지 알 수 있다.
위 트리는 K진 트리이다. (이때, N = (K + 1) * K + 2)
여기서 부모를 x, 자식을 A = {\(a_{1}\), ... , \(a_{K}\)} 라고 하자.
예를 들어서 x = 3 이라고 한다면, A = {\(a_{1}\), ... , \(a_{K}\)} = {2K + 2, 2K + 3, ... , 3K + 1} 이다.
이때 A 의 모든 원소에 2를 빼면 다음과 같다.
A = {2K, 2K + 1, 2K + 2, ..., 3K - 1} = {2K, 2K + 1, 2K + 2, ... , 2K + (K - 1)}
그리고 A의 모든 원소에 K를 더하면 다음과 같다.
A = {3K, 3K + 1, 3K + 2, ... , 3K + (K - 1)}
마지막으로 A의 모든 원소에 K를 나누면 다음과 같다.
A = {\(\frac{3K}{K}\), \(\frac{3K + 1}{K}\), \(\frac{3K + 2}{K}\), ... , \(\frac{3K + (K - 1)}{K}\)} = {3, 3, 3, ..., 3}
따라서 부모와 자식의 관계는 x = \(\frac{a_{i} + K - 2}{K}\) 임을 유추할 수 있다. (1 ≤ i ≤ K)
② ① 에서 부모와 자식의 관계를 이용하여 최소 공통 조상(LCA - Least Common Ancestor)를 구한다.
노드 x, y 에 대한 최소 공통 조상을 구하는 코드는 다음과 같다.
ll get_lca(ll x, ll y) {
map<ll, bool> m;
m[x] = m[y] = true;
while (x != 1) {
x = (x + K - 2) / K;
if (m[x]) return x;
m[x] = true;
}
while (y != 1) {
y = (y + K - 2) / K;
if (m[y]) return y;
}
}
③ 최소공통조상을 lca라고 할 때, (x에서 lca까지의 거리) + (y에서 lca까지의 거리) 가 결국 정답이 된다.
임의의 노드에서 lca까지의 거리를 반환하는 함수는 다음과 같다.
int go_lca(ll x, ll lca) {
int dist = 0;
while (x != lca) {
x = (x + K - 2) / K;
dist++;
}
return dist;
}
④ 이때 한가지 간과한 사실이 있다.
과연 K 값이 1일 경우 시간 초과 없이 진행이 가능한지에 대한 의문이다.
여기서 K = 1 인 경우와 K ≥ 2 인 경우에 대한 트리의 depth 가 어떠한지를 알아야 한다.
K = 1 인 경우의 트리의 depth는 다음과 같다.
- k = 1 인 경우 : 자식이 무조건 하나 존재하므로 트리의 depth는 N이 된다.
- 최악의 경우 O(N)이므로 시간 내에 해결할 수 없다. (N = \(10^{15}\))
- 따라서 K = 1 인 경우, |x - y| 를 통해 답을 구해야만 한다.
K ≥ 2 인 경우의 트리의 depth는 다음과 같다. (이때 K = 2 라고 가정)
- K ≥ 2 인 경우 : 자식이 K개 존재하므로 트리의 depth는 \(log_{K}N\) 이 된다.
- 최악의 경우 O(\(log_{K}N\)) 이므로 시간 내에 해결할 수 있다. (N = \(10^{15}\))
- 따라서 K ≥ 2 인 경우 최소 공통 조상을 이용해여 해결해야만 한다.
Solution Code
#define _CRT_SECURE_NO_WARNINGS
#include <string>
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <stack>
#define INF 1000000000
#define endl '\n'
#define ll long long
using namespace std;
ll N, K, Q;
void input() {
cin >> N >> K >> Q;
}
ll get_lca(ll x, ll y) {
map<ll, bool> m;
m[x] = m[y] = true;
while (x != 1) {
x = (x + K - 2) / K;
if (m[x]) return x;
m[x] = true;
}
while (y != 1) {
y = (y + K - 2) / K;
if (m[y]) return y;
}
}
int go_lca(ll x, ll lca) {
int dist = 0;
while (x != lca) {
x = (x + K - 2) / K;
dist++;
}
return dist;
}
void solution() {
ll x, y, lca;
while (Q--) {
cin >> x >> y;
if (K == 1) {
cout << abs(x - y) << endl;
}
else {
lca = get_lca(x, y);
cout << go_lca(x, lca) + go_lca(y, lca) << endl;
}
}
}
int main()
{
ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
input();
solution();
return 0;
}
'백준 > Tree' 카테고리의 다른 글
[백준 6549] 히스토그램에서 가장 큰 직사각형 (0) | 2022.01.14 |
---|---|
[백준 14725] 개미굴 (0) | 2021.12.26 |
[백준 2250] 트리의 높이와 너비 (0) | 2021.12.24 |
[백준 2042] 구간 합 구하기 (0) | 2021.07.29 |