본문 바로가기

Algorithms/PS

[PS] 백준 #1761. 정점들의 거리

 

문제 정보

 

문제

N(2 ≤ N ≤ 40,000)개의 정점으로 이루어진 트리가 주어지고 M(1 ≤ M ≤ 10,000)개의 두 노드 쌍을 입력받을 때 두 노드 사이의 거리를 출력하라.

 

나의 풀이

두 노드 사이의 거리는 두 노드를 잇는 최단경로의 거리로 정의되는데, 이 최단경로에는 항상 LCA가 포함된다. 따라서 이 문제는 두 노드의 LCA를 찾아서 최단 경로의 거리를 구하는 문제이다. (LCA에 관한 내용은 https://pongkijoa.tistory.com/78?category=543159 에 자세히 서술해두었다.)

이 문제의 핵심은 DFS를 돌 때 각 정점들의 거리 정보를 기록해두어야 한다는 점이다. 이때 거리는 임의의 한 정점에 대해서 구하면 된다. 여기서는 임의로 1번째 노드를 기준으로 거리 정보를 채웠다. 최단 경로는 u -> LCA -> v로 이 거리는 dist[way[0]] + dist[way[2]] - 2*dist[way[1]] 로 구하였다.

#include <bits/stdc++.h>

using namespace std;

#define SIZE 40000
#define MAX 17 // ceil(log2(100000));

int n, m;
int parent[SIZE + 1][MAX + 1];
int depth[SIZE + 1];
vector<vector<pair<int,int>>> adj(SIZE + 1);
vector<int> dist(SIZE + 1);

// dfs로 트리 제작하며 parent[i][0], depth 배열 채움 + dist 채움
void makeTreeByDFS(int curr, int dis) {
    for (auto next : adj[curr]) {
        if (depth[next.first] == -1) {
            parent[next.first][0] = curr;
            depth[next.first] = depth[curr] + 1;
            dist[next.first] = dis + next.second;
            makeTreeByDFS(next.first, dis + next.second);
        }
    }
}

int main() {
    cin.tie(NULL);	cout.tie(NULL);	std::ios::sync_with_stdio(false);

    // 정보 입력
    cin >> n;
    for (int i = 0; i < n - 1; i++) {
        int u, v, w; cin >> u >> v >> w;
        u--; v--;
        adj[u].push_back(make_pair(v, w)); // 0부터 시작
        adj[v].push_back(make_pair(u, w));
    }

    // 배열 초기화
    memset(parent, -1, sizeof(parent));
    fill(depth, depth + n, -1);
    depth[0] = 0; dist[0] = 0;
    makeTreeByDFS(0, 0);

    // parent 배열 채우기 (parent[i][j]: i번 노드의 2^j 층 위의 조상)
    for (int j = 0; j < MAX; j++) {
        for (int i = 1; i < n; i++) {
            if (parent[i][j] != -1) parent[i][j + 1] = parent[parent[i][j]][j];
        }
    }

    // Finding LCA
    cin >> m;
    while (m--) {
        int u, v; cin >> u >> v;
        u--; v--;
        int way[3]{}; // 경로
        way[0] = u; way[2] = v;

        // 1. 항상 depth[u] >= depth[v]
        if (depth[u] < depth[v]) swap(u, v);
        int diff = depth[u] - depth[v];

        // 2. u와 v가 같은 높이가 되도록 이동
        int j = 0;
        while (diff > 0) {
            if (diff % 2) u = parent[u][j];
            diff /= 2; j++;
        }

        // 3. u와 v가 다르면 2^j 높이 위로 이동 / 같으면 LCA 찾음
        if (u != v) {
            // 높이 2^17, 2^16, ..., 2^2, 2, 1 순으로 시도
            for (int j = MAX - 1; j >= 0; j--) {
                if (parent[u][j] != -1 && parent[u][j] != parent[v][j]) {
                    u = parent[u][j];
                    v = parent[v][j];
                }
            }
            // 두 정점 u, v의 부모가 같으므로 한 번 더 올림
            u = parent[u][0];
        }
        way[1] = u; 
        
        // 4. way 에 따라서 dist 값 구하기 (way[1]: LCA)
        // cout << "LCA 경로: " << way[0] << " -> " << way[1] << " -> " << way[2] << endl;
        int ans = dist[way[0]] + dist[way[2]] - 2*dist[way[1]];
        cout << ans << "\n";
    }
}

'Algorithms > PS' 카테고리의 다른 글

[PS] 백준 #11661. 해의 개수  (0) 2022.03.19
[PS] 백준 #24520. Meet In The Middle  (0) 2022.03.19
[PS] 백준 #2517. 달리기  (0) 2022.03.18
[PS] 백준 #10090. Counting Inversions  (0) 2022.03.18
[PS] 백준 #11509. 풍선 맞추기  (0) 2022.03.13