살짝 응용 다익스트라 (살응다)
다를 조금 응용해볼까?
-
문제
방향성이 없는 그래프가 주어진다. 세준이는 1번 정점에서 N번 정점으로 최단 거리로 이동하려고 한다. 또한 세준이는 두 가지 조건을 만족하면서 이동하는 특정한 최단 경로를 구하고 싶은데, 그것은 바로 임의로 주어진 두 정점은 반드시 통과해야 한다는 것이다.
세준이는 한번 이동했던 정점은 물론, 한번 이동했던 간선도 다시 이동할 수 있다. 하지만 반드시 최단 경로로 이동해야 한다는 사실에 주의하라. 1번 정점에서 N번 정점으로 이동할 때, 주어진 두 정점을 반드시 거치면서 최단 경로로 이동하는 프로그램을 작성하시오.
입력
첫째 줄에 정점의 개수 N과 간선의 개수 E가 주어진다. (2 ≤ N ≤ 800, 0 ≤ E ≤ 200,000) 둘째 줄부터 E개의 줄에 걸쳐서 세 개의 정수 a, b, c가 주어지는데, a번 정점에서 b번 정점까지 양방향 길이 존재하며, 그 거리가 c라는 뜻이다. (1 ≤ c ≤ 1,000) 다음 줄에는 반드시 거쳐야 하는 두 개의 서로 다른 정점 번호 v1과 v2가 주어진다. (v1 ≠ v2, v1 ≠ N, v2 ≠ 1)
출력
첫째 줄에 두 개의 정점을 지나는 최단 경로의 길이를 출력한다. 그러한 경로가 없을 때에는 -1을 출력한다.
문제 아이디어
- 문제의 요점은 주어진 v1과 v2를 지나는 최단 경로의 길이를 출력하는 것이다. 또한, 1번에서 출발하여 N번 도착지로 가는 방향도 정해져있다. 우리는 도착지까지 가는 사이에 저 2개의 경로를 껴넣으면 된다.
- 주어지는 경로지는 중복이 되지 않음으로, 다음과 같이 2가지의 방법이 있다는 것을 알 수 있다.
- 1) 출발지 -> v1 -> v2 -> 도착지
- 2) 출발지 -> v2 -> v1 -> 도착지
- 1번의 경우를 봤을 때, v1을 가는데 도중에 v2가 있어서 왔다갔다 하는 반례가 있지 않냐? 라고 할 수 있는데, 그것의 경우는 2번에서 카운트를 해주기 때문에 생각할 필요가 없다.
- 따라서 우리는 1번의 경우와 2번의 경우를 구해서, 더 작은 값을 가져가면 답을 얻을 수 있다.
문제 풀이
- 우리가 해야 될 일은 위에서 명시해 놓은 1) 과 2) 를 코드로 구현하면 된다. 최단거리를 구하는 방법은 다익스트라 를 참고하면 된다.
- 출발지를 기준으로 다익스트라
- v1을 기준으로 다익스트라
- v2를 기준으로 다익스트라
- 15번째 줄부터 시작하는
vector<ll> djk(int startNode)
함수는 다익스트라를 구하는 함수이다. - 53~55번째 줄부터는 위의 3가지 다익스트라 비용을 저장하는 벡터이다.
- 만약에 저 중에 하나라도 INF와 같은 값이 나온다는 것은, 경로가 없다는 것을 뜻한다.
- 문제가 어려운 것처럼 보이지만!? 사실은 짜장면에 고추가루 뿌리는 느낌의 그런,,,, 알죠?
Code
#include <iostream> | |
#include <vector> | |
#include <map> | |
#include <queue> | |
#include <algorithm> | |
#define ll long long | |
#define INF 987654321 | |
#define pii pair<long long, int> | |
using namespace std; | |
int N, E; | |
int v1, v2; | |
map<int, vector<pii>> adjList; | |
vector<ll> djk(int startNode){ | |
priority_queue<pii, vector<pii>, greater<pii>> PQ; | |
vector<ll> costVec(N+1); fill(costVec.begin(), costVec.end(), INF); | |
vector<int> visitVec(N+1); fill(visitVec.begin(), visitVec.end(), 0); | |
PQ.push({0, startNode}); | |
costVec[startNode] = 0; | |
while (!PQ.empty()){ | |
int popC, popN; | |
tie(popC, popN) = PQ.top(); PQ.pop(); | |
if (visitVec[popN] == 1) continue; | |
visitVec[popN] = 1; | |
for (auto& it: adjList[popN]){ | |
int nextC, nextN; | |
tie(nextN, nextC) = it; | |
if (visitVec[nextN] == 0){ | |
if (costVec[nextN] > costVec[popN] + nextC) { | |
costVec[nextN] = costVec[popN] + nextC; | |
PQ.push({costVec[nextN], nextN}); | |
} | |
} | |
} | |
} | |
return costVec; | |
} | |
int main(){ | |
ll answer = INF; | |
cin >> N >> E; | |
for (int i=0; i<E; i++){ | |
int a, b, c; | |
cin >> a >> b >> c; | |
adjList[a].push_back({b,c}); | |
adjList[b].push_back({a,c}); | |
} | |
cin >> v1 >> v2; | |
vector<ll> sGraph = djk(1); | |
vector<ll> v1Graph = djk(v1); | |
vector<ll> v2Graph = djk(v2); | |
ll r1, r2, r3; | |
// 1 -> v1 -> v2 -> N | |
r1 = sGraph[v1]; | |
r2 = v1Graph[v2]; | |
r3 = v2Graph[N]; | |
if (r1 != INF && r2 != INF && r3 != INF){ | |
answer = r1 + r2 + r3; | |
} | |
// 1 -> v2 -> v1 -> N | |
r1 = sGraph[v2]; | |
r2 = v2Graph[v1]; | |
r3 = v1Graph[N]; | |
if (r1 != INF && r2 != INF && r3 != INF){ | |
answer = answer > r1+r2+r3 ? r1+r2+r3 : answer; | |
} | |
if (answer != INF){ | |
cout << answer << '\n'; | |
} | |
else{ | |
cout << -1 << '\n'; | |
} | |
} |
오늘의 Tip!
너 for문 문제있어?
-
Python을 해본 사람이라면, Python 문법중에
for num in num_list
의 편리성을 알 것이다. 인덱스로 하나하나 접근하지 않고, 바로 원소를 뽑아낼 수 있는 for 문 말이다! C++ 에도 있다! 절망하지 마라! C++에서는 다음과 같은 문법을 통해 원소를 뽑아낼 수 있다.This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters#include <iostream> #include <vector> #include<algorithm> using namespace std; int main(){ //5크기의 벡터 만들고 4로 초기화 vector<int> numVector(5); fill(numVector.begin(), numVector.end(), 4); for (auto& num : numVector){ cout << num << ' '; } cout << endl; //5크기의 pair 벡터 만들고 4로 초기화 vector<pair<int, int>> pairVector = {{4,4},{4,4},{4,4},{4,4},{4,4}}; for (auto& it : pairVector){ cout << it.first << ':' << it.second << ' '; }cout << endl; } -
내가 벡터내의 자료형을 매번 명시해준다면 auto를 안써도 된다. 알아서 자료형을 찾아서 맞게끔 해주는게 auto인데, 시간이 아주 조금 더 걸리는데 (사실 알골 푸는데는 큰 지장이 없음) 취향에 맞게끔 사용하면 된다.
-
참조의 경우에는 C++ 여러곳에서 아주 유용하게 쓰인다. 참조에 대해서 잘 모른다면, 앞으로 포스팅할 포인터&참조 포스팅을 참고하면 될 것이다.