Coding Test/BaekJoon_C++

백준 22870 <산책 (large)> C++

JunOnJuly 2024. 4. 17. 17:37
728x90

https://www.acmicpc.net/problem/22870

 

22870번: 산책 (large)

코로나로 인하여 확찐자가 되버려 오늘부터 산책을 하려고 한다. 이를 위해 산책할 경로를 정하려고 한다. 현재 있는 곳 $S$에서 출발하여 $S$와 다른 곳인 $E$를 찍고 다시 $S$로 돌아오는 경로로

www.acmicpc.net


기본적으로 다익스트라를 두 번 사용해주면 풀 수 있는 문제입니다.

다만 최단 거리로 이동할 때 노드 번호가 가장 작은 노드로 이동하는 점이 해결해야 할 가장 큰 포인트라고 할 수 있습니다.

 

A -> B 최단거리를 기록하고 B 노드에서 다시 거슬러 올라가며 최단거리를 탐색할 때

 

1. 노드 번호가 작은 노드로 거슬러 올라가며 탐색한다.

의 경우에는 최단거리가 1 2 3 4 5 61 3 6 과 같이 주어졌을 경우 오류가 발생합니다.

2. 노드 번호가 큰 노드로 거슬러 올라가며 탐색한다.

의 경우에는 최단거리가 1 2 61 5 6 과 같이 주어졌을 경우 오류가 발생합니다.

 

이는 최단 거리의 직전 노드를 기록할 때 다음 노드의 관점에서 가장 작은 노드 번호를 기록하기 때문에 발생하는 문제이므로 위 방법으로 문제를 해결하려면

A -> B 탐색, B 노드에서 거슬러 올라가며 최단거리 모두 기록 -> A 노드에서 사전상 작은 순으로 재탐색

의 순서를 거쳐야 합니다.

 

그런데 다음 노드의 관점에서 가장 작은 노드 번호를 기록하는게 문제라면 A -> B 의 최단거리를 찾을 때 첫 탐색을 B -> A 의 방향으로 반전시켜 탐색하면 어떻게 되는지 생각해보겠습니다.

 

1. 모든 엣지는 양방향이므로 최단 거리는 변하지 않습니다.

2. B -> A 탐색 후 A 노드에서 거슬러 올라가며 A  -> B 최단 거리를 탐색하면 A -> B 의 관점에서는 현재 노드의 기준으로 직전 노드가 기록되어 있으므로 한 번의 탐색으로 조건을 만족하는 경로를 찾을 수 있습니다.

 

즉, S -> E 로의 첫 탐색을 E -> S 의 다익스트라로 탐색한 뒤 조건을 만족하는 최단거리를 찾아 해당 노드들을 제외시킨 뒤, S -> E , E -> S 상관 없이 탐색하여 최단거리를 찾아 더해주면 풀 수 있습니다.

 

E->S 방향으로 최단거리 찾기

 

최단거리에 속한 노드 제거하기

 

제거된 노드를 제외한 최단거리 찾기


#include <iostream>
#include <array>
#include <vector>
#include <algorithm>
#include <cmath>
#include <limits>
#include <deque>

using namespace std;

// 노드 정보를 기록할 구조체
struct Node {
    // 이동 가능한 노드, 거리
    vector<array<int, 2>> nodes;
    // 현재 노드까지 최단 거리
    int min_dist = numeric_limits<int>::max();
    // 현재 노드까지 최단 거리로 도달할 때 직전 노드들
    int front_node;
};

// 오름차순 정렬 함수
bool sort_dijk(array<int, 2>& arr_1, array<int, 2>& arr_2) {
    return arr_1[0] > arr_2[0];
}

// 다익스트라
array<Node, 200001> dijkstra(array<Node, 200001> graph, int& S, int& E, const vector<int>& remove_nodes = {}) {
    // 시작 노드 정리
    graph[S].min_dist = 0;
    graph[S].front_node = 0;
    // 큐
    deque<array<int, 2>> hq = { {0, S} };
    // 순회
    while (!hq.empty()) {
        // 팝 준비
        pop_heap(hq.begin(), hq.end(), sort_dijk);
        // 현재 거리, 현재 노드 번호
        int now_dist = hq.back()[0];
        int now_num = hq.back()[1];
        // 현재 노드
        Node now_node = graph[now_num];
        // 팝
        hq.pop_back();
        // 현재 거리가 현재 노드까지의 최단거리보다 길면 패스
        if (now_dist > now_node.min_dist) continue;
        // 이동 가능 노드 순회
        for (auto node : now_node.nodes) {
            // 다음 노드 번호, 다음 노드까지 거리
            int next_num = node[0];
            int dist = node[1];
            // 다음 노드가 삭제된 노드에 포함되면 패스
            if (find(remove_nodes.begin(), remove_nodes.end(), next_num) != remove_nodes.end()) continue;
            // 다음 노드까지 총 거리
            int next_dist = now_dist + dist;
            // 다음 노드까지 거리가 다음 노드까지의 최단거리보다 짧으면
            if (next_dist < graph[next_num].min_dist) {
                // 다음 노드가 목적 노드가 아니면
                if (next_num != E) {
                    // 큐에 추가
                    hq.push_back({ next_dist, next_num });
                }
                // 최단거리 최신화
                graph[next_num].min_dist = next_dist;
                // 최단거리 직전노드 최신화
                graph[next_num].front_node = now_num;
            }
            // 다음 노드까지 거리가 다음 노드까지의 최단거리와 같으면
            else if (next_dist == graph[next_num].min_dist) {
                // 최단거리 직전노드 추가
                graph[next_num].front_node = min(now_num, graph[next_num].front_node);
            }
        }
    }
    return graph;
}

int main(void){
    // 입력
    int N, M;
    cin >> N >> M;
    // 노드 정보를 저장할 그래프
    array<Node, 200001> graph;
    for (int i = 0; i < M; i++) {
        int A, B, C;
        cin >> A >> B >> C;
        graph[A].nodes.push_back({ B, C });
        graph[B].nodes.push_back({ A, C });
    }
    int S, E;
    cin >> S >> E;
    // S -> E 다익스트라
    // S -> E 의 거리를 계산해야 하지만 우선 양방향 간선이므로 거리 차이는 없음
    // 사전순으로 가장 먼저 오는 것을 선택해야 하지만 S -> E 다익스트라를 사용하면
    // 현재 노드에서 보기에 사전적으로 가장 작은 노드가 아닌
    // 다음 노드에서 보기에 사전적으로 가장 작은 노드가 최단 거리에 기록됨
    // -> 현재 노드에서 최단거리를 기준으로 큰 노드에 접근하면 큰 노드에서는 현재 노드를 먼저 기록하기 때문
    // 때문에 E -> S 로 다익스트라를 사용하면 순서가 반전되기 때문에 S -> E 기준으로 현재 노드에서 보기에 사전적으로 가장 작은 노드가 기록됨
    array<Node, 200001> STOE = dijkstra(graph, E, S);
    // S -> E 거리
    int STOE_dist = STOE[S].min_dist;
    // 최단거리 탐색 및 삭제할 노드 기록
    // 삭제할 노드
    vector<int> remove_nodes;
    // 탐색 시작 노드
    int now_node = S;
    while (true) {
        // 다음 노드
        int next_node = STOE[now_node].front_node;
        // 다음 노드가 끝 노드면 끝
        if (next_node == E) break;
        // 삭제 노드 기록
        remove_nodes.push_back(next_node);
        // 다음 탐색 시작 노드
        now_node = next_node;
    }
    // E -> S 거리
    array<Node, 200001> ETOS = dijkstra(graph, E, S, remove_nodes);
    int ETOS_dist = ETOS[S].min_dist;

    cout << STOE_dist + ETOS_dist;
}

 

728x90