컴퓨터 사이언스/1고리즘

백준 5719 : 거의 최단 경로

저세상 개발자 2020. 12. 30. 00:54

www.acmicpc.net/problem/5719

 

5719번: 거의 최단 경로

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스의 첫째 줄에는 장소의 수 N (2 ≤ N ≤ 500)과 도로의 수 M (1 ≤ M ≤ 104)가 주어진다. 장소는 0부터 N-1번까지 번호가 매겨져 있

www.acmicpc.net

이 문제는 최단 경로에 포함되는 간선을 이용하지 않고 이동하였을 때 목적지까지의 최단 경로를 구하는 문제이다.

 

다음과 같은 로직으로 문제를 해결하였다.

 

1. 모든 간선을 사용할 수 있는 상태에서 다익스트라 알고리즘으로 최단 경로를 찾는다. 이 때 최단 경로 까지 오는 데 지나 온 모든 길을 저장한다.

2. 지나온 길을 되돌아가면서 길을 삭제한다.

3. 다시 다익스트라 알고리즘을 사용하여 최단 경로를 찾는다.

 

지나온 길을 되돌아가면서 삭제할 때 입력받아둔 간선을 찾아서 지워야하는데 벡터 자료구조를 사용 시 해당 노드에서 해당 간선을 찾는데 최악의 경우 O(n)의 시간이 걸리므로 이를 방지하기 위하여 O(1)의 시간복잡도로 원하는 간선에 접근할 수 있는 unordered_map 자료구조를 사용하였다. 

코드로 구현하면 아래와 같다.

#include <iostream>
#include <vector>
#include <queue>
#include <string>
#include <unordered_map>

#define INF 1e9

using namespace std;

int N, M, s, d;
int dist[500];
vector<int> path[500];
unordered_map<int, int> cost[500];

int dijkstra() {
	int cur_pos, cur_dist, next_pos, next_dist;
	unordered_map<int, int>::iterator iter;
	priority_queue<pair<int, int>> pq;
	
    	//다익스트라 알고리즘을 사용하여 최단 경로를 구하고 경로를 저장한다.
    	pq.emplace(0, s);
	dist[s] = 0;
	while (!pq.empty()) {
		cur_dist = -pq.top().first;
		cur_pos = pq.top().second;
		pq.pop();

		if (dist[cur_pos] < cur_dist) continue;
		for (iter = cost[cur_pos].begin(); iter != cost[cur_pos].end(); ++iter) {
			next_pos = iter->first;
			next_dist = cur_dist + iter->second;
			if (dist[next_pos] > next_dist) {
				if (path[next_pos].size() != 0) {
					path[next_pos].clear();
				}
                		//path에는 해당 노드로 오기 직전 노드 번호를 저장
				path[next_pos].emplace_back(cur_pos);
				dist[next_pos] = next_dist;
				pq.emplace(-next_dist, next_pos);
			}
            		//동일한 dist값으로 도달한 값도 저장해준다.
            		//최단 경로가 하나가 아닐수도 있기 때문.
			else if (dist[next_pos] == next_dist) {
				path[next_pos].emplace_back(cur_pos);
			}
		}
	}

	//최단 경로를 역추적하면서 가중치를 INF로 바꿔 비활성화
	int prev_pos;
	queue<int> q;
	q.emplace(d);
	while (!q.empty()) {
		cur_pos = q.front();
		q.pop();
		for (int i = 0; i < path[cur_pos].size(); ++i) {
			prev_pos = path[cur_pos][i];
			if (cost[prev_pos][cur_pos] < INF) {
				cost[prev_pos][cur_pos] = INF;
				q.emplace(prev_pos);
			}
		}
	}

	for (int i = 0; i < N; ++i) dist[i] = INF;

	//다시 다익스트라 알고리즘으로 최단경로 구하기
	pq.emplace(0, s);
	dist[s] = 0;
	while (!pq.empty()) {
		cur_dist = -pq.top().first;
		cur_pos = pq.top().second;
		pq.pop();

		if (dist[cur_pos] < cur_dist) continue;
		for (iter = cost[cur_pos].begin(); iter != cost[cur_pos].end(); ++iter) {
			if (iter->second == INF) continue;
			next_pos = iter->first;
			next_dist = cur_dist + iter->second;
			if (dist[next_pos] > next_dist) {
				dist[next_pos] = next_dist;
				pq.emplace(-next_dist, next_pos);
			}
		}
	}

	return dist[d] != INF ? dist[d] : -1;
}

int main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	int from, to, tmp_cost;

	while (1) {
		cin >> N >> M;
		if (N + M == 0) break;
		cin >> s >> d;

		for (int i = 0; i < N; i++) {
			dist[i] = INF;
			cost[i].clear();
			path[i].clear();
		}
		for (int i = 0; i < M; ++i) {
			cin >> from >> to >> tmp_cost;
			cost[from][to] = tmp_cost;
		}
		cout << dijkstra() << '\n';
	}

	return 0;
}
메모리: 2840 kb 시간: 44 ms

 

여담으로 이 문제를 풀면서 path 값을 초기화를 시켜주지 않아서 디버깅에 애를 먹었는데,

코드를 처음부터 제대로 계획하고 짠게 아니라 짜면서 미흡한 부분을 계속 고쳐나가다 보니까 이런 현상이 발생한 것 같다. 항상 문제를 풀기 전에 나름대로 이렇게 풀어야겠다 계획을 세우고 코드를 짜기 시작하는데 중간에 로직 오류가 발견되어서 고치고 고치다 스파게티 소스가 되는 경우가 많다. 아직도 많이 부족한가보다. 열심히 하자.