1. 문제
2. 입 출력
3. 예제
4. 문제 해설
이 문제는 lca를 이용한 문제였다. 문제 자체에서 가장 root노드가 무엇인지 주진 않았지만 1번 노드를 root로 삼아 (왜냐하면 1번 노드는 항상 나오기 때문이다.) 트리를 구성하고 그 후 효율적인 lca풀이를 이용하면 충분히 구할 수 있는 문제이다.
이 문제는 또한 lca를 구성하는 과정에서 구간의 최댓값과 최솟 값을 저장하며 가야하는 문제이다. 따라서 dp문제의 성향이 강하다고 할 수 있겠다.
우선 효율적인 lca 풀이를 위해 tree를 구성해준다. 나는 bfs를 통해 트리를 만들어주었다.
void makeTree() {
depth[1] = 1;
q.push({ 1 });
while (q.size()) {
int curr = q.front(); q.pop();
for (auto next : AL[curr]) {
if (!depth[next.first]) {
depth[next.first] = depth[curr] + 1;
parent[next.first][0] = curr;
min_road[next.first][0] = next.second;
max_road[next.first][0] = next.second;
q.push(next.first);
}
}
}
}
dp배열 즉 depth는 트리의 height를 의미하고 parent는 그 노드의 부모를 의미한다. 또한 효율적인 lca풀이와 마찬가지로
parent배열과 min_road배열 max_road배열 세 가지를 각각 이차원 배열로 선언했는데 이 때 모두 parent[first][second]의 경우 first노드의 2 ^ second 부모의 값 또는 min_road[first][second]의 경우는 first노드 부터 2 ^ second부모 까지 도로의 최솟값 max_road는 최댓값을 의미한다.
위와 같이 트리를 만들어주고 각각의 dp배열의 0번째를 채우고 나면 이제 dp배열의 초기값을 채웠다 할 수 있겠다. 이제 dp배열을 만들어준다.
void makeParent() {
for (int k = 0; k < HEIGHT - 1; k++) {
for (int x = 1; x <= N; x++) {
if (parent[x][k] != 0) {
parent[x][k + 1] = parent[parent[x][k]][k];
max_road[x][k + 1] = max(max_road[x][k], max_road[parent[x][k]][k]);
min_road[x][k + 1] = min(min_road[x][k], min_road[parent[x][k]][k]);
}
}
}
}
parent[x][k] = parent[parent[x][k-1]][k-1] 의 점화식을 따른다. 이는 parent 2^k = 2 ^ (k-1) + 2 ^ (k-1)의 점화식 성질을 따른 것이다.
void lca2(int l, int r) {
if (depth[l] < depth[r]) swap(l, r);
int diff = depth[l] - depth[r];
int cnt = 0;
int mn = INF, mx = 0;
while (diff) {
if (1 & diff) {
mn = min(min_road[l][cnt], mn);
mx = max(max_road[l][cnt], mx);
l = parent[l][cnt];
}
cnt++;
diff >>= 1;
} // 둘의 depth를 맞춘다.
//printf("afterl : %d after r : %d\n", l, r);
if (l != r) {
for (int i = HEIGHT - 1; i >= 0; i--) {
if (parent[l][i] != parent[r][i]) {
mn = min(min_road[l][i], mn);
mn = min(min_road[r][i], mn);
mx = max(max_road[l][i], mx);
mx = max(max_road[r][i], mx);
l = parent[l][i];
r = parent[r][i];
}
}
if (min_road[l][0] != 0 && min_road[r][0] != 0) {
mn = min(min_road[l][0], mn);
mn = min(min_road[r][0], mn);
}
if (max_road[l][0] != 0 && max_road[r][0] != 0) {
mx = max(max_road[l][0], mx);
mx = max(max_road[r][0], mx);
}
}
printf("%d %d\n", mn, mx);
}
위의 세 dp배열을 채워줬으면 이제 lca로직을 수행해야한다. 우선 둘의 높이를 맞춘 후 맨 위의 조상부터 차례대로 검사하여 다르다면 그 위치로 이동해가는 방식을 택한다. 위의 로직의 자세한 설명은 lca2문제를 풀며 설명해두었다.
위의 로직을 모두 통과하면 정답은 그 l과 r의 부모가 되므로 root가 아니라면 한 번더 수행해주어야한다.
5. 코드
/*
sparse table을 유도한다. (K와 N이 크기 때문이다.)
*/
#pragma warning (disable : 4996)
#include <cstdio>
#include <vector>
#include <algorithm>
#include <queue>
#define MAX 100000
#define INF 0x3f3f3f3f
#define HEIGHT 21
using namespace std;
int N, K;
vector<pair<int, int>>AL[MAX + 2];
int parent[MAX][HEIGHT];
int depth[MAX];
int min_road[MAX][HEIGHT];
int max_road[MAX][HEIGHT];
queue<int> q;
void makeTree() {
depth[1] = 1;
q.push({ 1 });
while (q.size()) {
int curr = q.front(); q.pop();
for (auto next : AL[curr]) {
if (!depth[next.first]) {
depth[next.first] = depth[curr] + 1;
parent[next.first][0] = curr;
min_road[next.first][0] = next.second;
max_road[next.first][0] = next.second;
q.push(next.first);
}
}
}
}
void makeParent() {
for (int k = 0; k < HEIGHT - 1; k++) {
for (int x = 1; x <= N; x++) {
if (parent[x][k] != 0) {
parent[x][k + 1] = parent[parent[x][k]][k];
max_road[x][k + 1] = max(max_road[x][k], max_road[parent[x][k]][k]);
min_road[x][k + 1] = min(min_road[x][k], min_road[parent[x][k]][k]);
}
}
}
}
void lca2(int l, int r) {
if (depth[l] < depth[r]) swap(l, r);
int diff = depth[l] - depth[r];
int cnt = 0;
int mn = INF, mx = 0;
while (diff) {
if (1 & diff) {
mn = min(min_road[l][cnt], mn);
mx = max(max_road[l][cnt], mx);
l = parent[l][cnt];
}
cnt++;
diff >>= 1;
} // 둘의 depth를 맞춘다.
//printf("afterl : %d after r : %d\n", l, r);
if (l != r) {
for (int i = HEIGHT - 1; i >= 0; i--) {
if (parent[l][i] != parent[r][i]) {
mn = min(min_road[l][i], mn);
mn = min(min_road[r][i], mn);
mx = max(max_road[l][i], mx);
mx = max(max_road[r][i], mx);
l = parent[l][i];
r = parent[r][i];
}
}
if (min_road[l][0] != 0 && min_road[r][0] != 0) {
mn = min(min_road[l][0], mn);
mn = min(min_road[r][0], mn);
}
if (max_road[l][0] != 0 && max_road[r][0] != 0) {
mx = max(max_road[l][0], mx);
mx = max(max_road[r][0], mx);
}
}
printf("%d %d\n", mn, mx);
}
void input() {
scanf("%d", &N);
for (int i = 0; i < N - 1; i++) {
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
AL[a].push_back({ b,c });
AL[b].push_back({ a,c }); // 인접리스트
}
scanf("%d", &K);
}
int main() {
input();
makeTree();
makeParent();
for (int i = 0; i < K; i++) {
int d, e;
scanf("%d %d", &d, &e);
lca2(d, e);
}
return 0;
}
첫 번째는 그냥 lca풀이로 풀다가 시간초과가 나버렸다.
'c++ > 알고리즘' 카테고리의 다른 글
[C++] 백준 - 12784 인하니카 공화국 (0) | 2023.05.27 |
---|---|
[c++] 카카오 개인정보 수집 유효기간 (문자열 다루기) (0) | 2023.04.27 |
[c++] 백준-최대공약수 하나 빼기 ( 누적합, 정수론) (1) | 2023.02.28 |
[c++] 백준 - 케이크 자르기2 10714 (0) | 2023.02.27 |
[c++] 백준 - 1655 가운데를 말해요(우선순위 큐) (1) | 2023.02.16 |
댓글