파티

백준 1238

GOLD III


  1. 문제 내용
  2. 해결 방안
  3. 풀이 코드

문제 내용

problem.png

문제 링크

해결 방안

N개의 마을과 N개의 마을 중 임의의 마을 하나와의 최단 거리를 구하는 문제이다. 단, 각 마을은 유향 그래프로 연결되어 있어 마을 사이를 오고 가는 최단거리가 다를 수 있다. 정리하자면, (N-1)개의 노드에서 임의의 노드로 가는 최단 거리와 임의의 노드에서 (N-1)개의 노드로 가는 최단 거리를 모두 구하고, 두 최단 거리의 합이 가장 큰 노드의 왕복을 위해 소요된 시간을 출력하면 된다.

문제에서 주어진 각 노드 사이의 간선의 가중치는 모두 1~100 사이의 자연수이므로, 다익스트라 알고리즘을 사용할 수 있다. 하지만 각 노드에 대해 다익스트라 알고리즘을 사용하기에는 노드의 개수가 1,000개까지 있을 수 있으며, 왕복까지 총 2번의 반복을 해야 하기 때문에 제한 시간 내에 해결할 수 없다. 하지만, 다익스트라 알고리즘의 과정을 잘 생각해보면, 시작 노드로부터 임의의 정점까지의 최단 거리를 찾는 도중에 방문하는 노드들과의 거리는 모두 최단 거리라는 것을 알 수 있다. 이러한 성질을 이 문제에 적용하면, (N-1)개의 모든 노드들을 방문할 때까지 다익스트라 알고리즘을 수행하여 임의의 노드로부터 직간접적으로 연결된 모든 노드와의 최단거리를 한 번에 구하는 것이 가능하다는 것을 알 수 있다.

반대로, (N-1)개의 노드로부터 임의의 노드까지의 최단 거리는 유향 그래프의 방향성 때문에 위의 방법을 사용할 수 없다. 하지만, 유향 그래프의 방향만 반대로 뒤집으면, 가중치는 그대로인 채로 방향만 임의의 노드에서 (N-1)개의 노드로 가는 그래프가 만들어진다. 결론적으로, 모든 간선들을 입력받으면서 방향만 반대로 뒤집은 간선들을 따로 만들어 저장한 뒤, 두 종류의 간선에 대해 각각 임의의 노드부터 (N-1)개의 노드로 가는 최단 거리를 모두 구하고 각 노드의 최단 거리 합이 가장 큰 노드를 찾으면 된다.

다익스트라 알고리즘(dijkstra algorithm)이란?

그래프 내에서 각 노드 사이에 연결된 간선들 모두가 음의 가중치를 갖지 않는 경우에 대해, 임의의 두 정점 사이의 최단거리를 구하는 알고리즘이다. 알고리즘을 처음 고안안 컴퓨터과학자인 에츠허르 다익스트라의 이름을 따서 명명되었다.

다익스트라 알고리즘을 통해 노드 사이의 최단 거리를 찾는 과정은 다음과 같다.

  1. 최단 거리를 구하고자 하는 임의의 한 노드로부터 시작한다.
  2. 시작하는 노드의 가중치를 0으로 초기화하고, 그 외의 모든 노드들에는 임의의 큰 값을 부여한다.
  3. 현재 노드와 인접한 노드들에 대해 (현재 노드의 가중치 + 인접 노드로의 간선의 가중치)와 인접 노드의 가중치를 비교한다.
  4. 만약 인접 노드의 가중치보다 (현재 노드의 가중치 + 인접 노드로의 간선의 가중치)가 더 크면, 해당 노드의 가중치를 갱신한다.
  5. 위의 3~4까지의 과정을 아직 방문하지 않은 가장 작은 가중치를 가진 노드에 대해 반복한다.
  6. 처음으로 시작 노드 외의 최단 거리를 찾고자 하는 노드에 방문했거나, 방문할 수 있는 모든 노드에 대해 방문했을 경우 반복을 중단한다.
  7. 위 과정을 통해 목적지인 노드에 방문했다면 해당 노드의 가중치가 최단 거리이고, 만약 노드의 가중치가 처음에 부여한 임의의 큰 값이라면 노드끼리 직간접적으로 연결되지 않은 것을 의미한다.

결과적으로, 연결된 모든 노드를 방문하고 방문한 노드의 모든 간선에 대해 확인해야 하며(O(V+E)), 간선을 통해 확인한 노드들의 가중치를 통해 다음 노드를 우선순위 큐나 힙으로 결정해야 한다(O(log₂V)). 때문에 연결된 유효한 노드와 간선의 개수가 각각 V와 E라고 한다면, 시간 복잡도는 O((V+E)log₂V)가 나온다.

풀이 코드

#include <iostream>
#include <vector>
#include <map>
#include <queue>

using namespace std;
using Edge = pair<int, int>;
using Weight = pair<int, int>;

// 다익스트라 알고리즘(Dijkstra Algorithm)
vector<int> dijkstra(map<int, vector<Edge>> adjMatrix, int x) {
    // 현재 가장 가중치가 작은 노드를 찾기 위한 우선순위 큐(힙 트리 구조)
    priority_queue<Weight, vector<Weight>, greater<Weight>> heap;
    // 큐에서 꺼낸 값 저장
    Weight top;
    // 임의의 노드 x에서 n개의 노드로 가는 최단 거리를 저장
    vector<int> x2n(1001, 1000000);

    // 시작 노드의 가중치를 0으로 초기화
    x2n[x] = 0;
    heap.push({0, x});
    while(!heap.empty()) {
        // 가장 가중치가 작은 노드
        top = heap.top();
        heap.pop();
        
        // 현재 노드의 가중치가 이미 최단 거리면 알고리즘 수행 안함
        if(x2n[top.second] < top.first)
            continue;

        for(auto h: adjMatrix[top.second]) {
            // 각 노드의 가중치보다 갱신할 가중치가 작으면 노드의 가중치 변경
            // 가중치가 변경되면 갱신할 가중치에 대해 우선순위 큐 갱신
            if(x2n[h.first] > h.second + top.first) {
                x2n[h.first] = h.second + top.first;
                heap.push({h.second + top.first, h.first});
            }
        }
    }
    // 각 노드까지의 최단 거리 반환
    return x2n;
}

int main() {
    // 노드의 개수, 간선의 개수, 임의의 노드 입력
    int n, m, x;
    cin >> n >> m >> x;

    // 간선의 출발 노드, 도착 노드, 가중치 저장
    // 이때, 방향만 반대로 한 간선을 만들어 별도로 저장
    int s, e, t;
    map<int, vector<Edge>> adjMatrix;
    map<int, vector<Edge>> reversedAdjMatrix;
    for(int i = 0; i < m; i++) {
        cin >> s >> e >> t;
        adjMatrix[s].push_back({e, t});
        reversedAdjMatrix[e].push_back({s, t});
    }

    // (n-1)개의 노드에서 x로 가는 최단 거리와
    // x에서 (n-1)개의 노드로 가는 최단 거리 계산
    vector<int> n2x = dijkstra(reversedAdjMatrix, x);
    vector<int> x2n = dijkstra(adjMatrix, x);

    // 두 최단 거리의 합이 가장 큰 노드 탐색 및 출력
    int maxCost = 0;
    for(int i = 1; i <= n; i++) {
        if(maxCost < n2x[i] + x2n[i])
            maxCost = n2x[i] + x2n[i];
    }
    cout << maxCost << endl;
    return 0;
}