1. 문제
2. 입출력 및 예제
3. 문제 해설
백준 - 1916 최소비용 구하기 문제와 비슷한 문제이다.
하지만 추가된 것은 몇 개의 노드를 거치면서 왔는 지에 대한 정보와 지나온 path에 대한 정보도 출력을 해야한다는 것이다.
처음에 예제 답안을 살펴보면 경로가 1 3 5로 나와있는 것을 확인할 수 있다.
하지만 위의 그래프를 그려보면 1 3 5 뿐만 아니라 1 4 5도 된다는 것을 알 수 있다. 그리고 경로에 대한 제약사항은 존재하지 않는다. 따라서 이 문제는 도달할 수 있는 경로가 여러가지일 경우에는 그냥 아무거나 출력하면 되는 문제였다.
여태까지 많은 경로를 기억했다가 출력하는 문제들을 풀어본 결과 경로를 기억하는 문제들은 대게 그 경로에 추가되는 로직이 있기 마련이다. 또한 위의 문제는 다익스트라 알고리즘을 이용해서 푸는 문제이기 때문에 greedy알고리즘을 기반으로 한다. 즉, 한번 이 노드가 최소 값으로 갱신됐으니 다시 살펴보지 않는다는 전제하에 앞으로의 경로를 탐색하는 것이다.
나는 처음에 한 도시에서 다른 도시로 이동할 때 한 개의 루트만 존재할 수 있는 줄 알았다. 따라서 방문처리에 대한 부분은 신경쓰지 않은 채 그저 다익스트라 알고리즘을 이용해 진행했고 결과는 시간초과였다.
만약 1번 도시에서 2번 도시로 갈 수 있는 길이 2개이고 그 cost가 각각 10과 2였다면 다익스트라 알고리즘을 통해 목적지가 2이고 cost가 10인 길을 먼저 탐색할 것이다. 그렇게 되면 1번 노드에서 2번 노드로 가는 길을 10으로 한 것이 priority_queue안에 들어갈 것이다.
pq 안에는 {node : 2, cost : 10}; 가 먼저 들어간다.
그 다음 탐색하는 것이 목적지가 2이고 cost가 2인 길일 것이다. 이 경우에도 기존에 목적이 2번의 거리가 앞서 10으로 업데이트 된 값보다 작으므로 pq안에 들어가게 된다.
pq안에는 {node:2 , cost: 10} , {node : 2, cost : 2}가 들어가지만 pq의 특성 상 다음과 같이 정렬이 될 것이다.
pq : {node :2, cost : 2} , {node : 2, node : 10}
이렇게 되면 2번 노드부터 꺼내서 다시 다익스트라 알고리즘을 수행한다.
하지만 그 다음 노드 또한 노드 2번에 해당한다. 만약 이 노드에 대해 또 다익스트라 알고리즘을 수행해준다면 우리는 사실상 결과에는 영향을 주지 않는(어차피 처음에 업데이트 된 값이 최단경로일테니...(pq의 성질에 의해)) 쓸데없는 다익스트라 알고리즘을 수행하게 되는 것이다. 따라서 다음과 같은 처리가 필요하다.
if(dist[curr.node] < curr.cost) continue; // 이미 방문을 한 노드에 대해서는 더 이상 진행하지 않고 넘긴다.
경로 처리
우리가 일반적으로 사용하는 다익스트라 알고리즘은 pq를 이용해 다음과 같이 구현된다.
vector<n_t>AL[MAX]; // 간선 정보가 들어있는 인접리스트
void dijkstra(int root) {
priority_queue<n_t> pq;
dist[root] = 0;
pq.push({root,0});
while(pq.size()) {
n_t curr = pq.top(); pq.pop();
if(dist[curr] < curr.cost) continue; // 방문 처리
for(n_t next : AL[curr.node]) {
int cost = dist[curr.node] + next.cost;
if(dist[next.node] > cost) {
dist[next.node] = cost;
pq.push({next.node, cost});
}
}
}
}
위의 다익스트라 알고리즘에서 최단 경로를 갱신하는 부분은 바로 다음과 같다.
int cost = dist[curr.cost] + next.cost
if(dist[next.node] > cost) {
dist[next.node] = cost;
pq.push({next.node, cost});
}
이 부분에서 어떤 일이 생기는 지 살펴보면 next.node로 이동할 때 기존의 이동경로보다 curr에서 이동하는 것이 더 짧은 경로니까 갱신해줘! 라는 뜻이다. 따라서 next를 가기 전은 무조건 curr을 거쳐 간다는 것을 알 수 있다. 따라서 나는 다음과 같이 이전 경로를 기억해주었다.
int dist[MAX];
vector<n_t> AL[MAX];
int prevPath[MAX];
vector<int> path;
int root, leaf;
void dijkstra(int root) {
priority_queue<n_t> pq;
for (int i = 1; i <= n; i++) {
dist[i] = INF;
} // 무한대로 초기화
dist[root] = 0;
pq.push({ root, 0 });
while (pq.size()) {
n_t curr = pq.top(); pq.pop();
if (dist[curr.node] < curr.cost) continue;
for (n_t next : AL[curr.node]) {
int cost = dist[curr.node] + next.cost;
if (dist[next.node] > cost) {
dist[next.node] = cost;
prevPath[next.node] = curr.node;
pq.push({ next.node, cost });
}
}
}
}
그렇다면 prevPath라는 배열에는 현재 경로로 도착하기 전 어디서 왔는가에 대한 정보가 저장될 것이다. 그렇다면
거꾸로 따라가면 된다.
예제를 돌려 prevPath와 dist를 출력해보면 다음과 같다.
예제에서 1번에서 5번으로 가는 길을 출력하라 했으므로 prePath[5]번부터 살펴본다. 4에 해당한다. 4번에서 왔다는 것이다.
그러면 다시 prevPath[4]를 확인한다. 4는 어디서 왔을까? -> prevPath[4] = 1이므로 1에서 온 것을 확인할 수 있다.
그렇다면 1로 이동한다. prePath[1] = 0이다. 시작점이라는 뜻이다. 따라서 종료한다. 이 과정에서 몇 개의 도시를 거쳤는 지 경로는 어디인지 모두 확인할 수 있다. 나는 코드를 다음과 같이 구성했다.
4. 코드
#pragma warning (disable : 4996)
#include <cstdio>
#include <vector>
#include <queue>
#define MAX 1001
#define BUSMAX 100001
#define INF 0x3f3f3f3f
using namespace std;
int n, m;
struct n_t {
int node;
int cost;
inline bool const operator < (const n_t& ref) const {
return this->cost > ref.cost;
}
};
int dist[MAX];
vector<n_t> AL[MAX];
int prevPath[MAX];
vector<int> path;
int root, leaf;
void input() {
scanf("%d %d", &n, &m);
for (int i = 0; i < m; i++) {
int s, e, c;
scanf("%d %d %d", &s, &e, &c);
AL[s].push_back({ e,c }); //인접리스트 생성
}
}
void dijkstra(int root) {
priority_queue<n_t> pq;
for (int i = 1; i <= n; i++) {
dist[i] = INF;
} // 무한대로 초기화
dist[root] = 0;
pq.push({ root, 0 });
while (pq.size()) {
n_t curr = pq.top(); pq.pop();
if (dist[curr.node] < curr.cost) continue;
for (n_t next : AL[curr.node]) {
int cost = dist[curr.node] + next.cost;
if (dist[next.node] > cost) {
dist[next.node] = cost;
prevPath[next.node] = curr.node;
pq.push({ next.node, cost });
}
}
}
}
int cnt = 1;
int main() {
input();
scanf("%d %d", &root, &leaf);
dijkstra(root);
/*printf("distance --\n");
for (int i = 1; i <= n; i++) {
printf("%d ", dist[i]);
}
printf("\n");
printf("prevPath --\n");
for (int i = 1; i <= n; i++) {
printf("%d ", prevPath[i]);
}
printf("\n");*/
int idx = leaf;
path.push_back(leaf);
while (1) {
if (prevPath[idx] == 0) break;
cnt++;
path.push_back(prevPath[idx]);
idx = prevPath[idx];
}
printf("%d\n%d\n",dist[leaf], cnt);
for (int i = path.size() - 1; i >= 0; i--) {
printf("%d ", path[i]);
}
return 0;
}
'c++ > 알고리즘' 카테고리의 다른 글
[c++] 백준 - 1655 가운데를 말해요(우선순위 큐) (1) | 2023.02.16 |
---|---|
[c++] 백준 - 2014 소수의 곱 (우선순위 큐) (0) | 2023.02.16 |
[c++] 백준-10999 구간합구하기2 (세그먼트트리, lazy_loading) (0) | 2023.02.15 |
[c++] 백준 - 11066 파일 합치기 (dp) (0) | 2023.02.14 |
[c++] 백준 - 1520 내리막길 (DFS + DP) (0) | 2023.02.14 |
댓글