[백준] 1738번 골목길

2024. 11. 20. 21:17·백준 문제/Bellman-Ford

문제: 1738번: 골목길

 

#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <iomanip> // setprecision을 사용하기 위한 헤더
#include <climits>

using namespace std;

int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int n, m; // n: 교차 지점 수(정점), m: 골목길의 개수(간선)
	cin >> n >> m;
	vector<pair<pair<int, int>, int>> edges; // 간선들 저장해놓기
	vector<vector<int>> reverseEdges(n + 1); // 역방향 간선들 저장해놓기(정점 n 에 도달할 수 있는 노드들을 찾기 위함.
	vector<int> prev(n + 1); // 이전 정점 저장해놓기

	for (int i = 0; i < m; i++) {
		int u, v, w;
		cin >> u >> v >> w; // u 번 교차점에서 v 번 교차점으로 이동할 수 있는 골목길, w 는 금품의 양
		
		// 답으로 코레스크 콘도에 도착하는 경로 중 금품의 양이 최대가 되는 경로를 원하므로 입력받은 가중치를 -1 랑 곱해줌
		// 이제 기존의 최단 거리 벨만포드 풀이를 이용하면 됨
		w = w * -1; 
		
		edges.push_back(make_pair(make_pair(u, v), w)); // 간선 저장

		reverseEdges[v].push_back(u); // 역방향 간선 저장(가중치는 필요 없음)
	}

	// 정점 n 과 연결되는 정점들 다 표시해놓기(음의 사이클이 이루어지는 곳의 정점이 정점 n 이랑 연결되면 -1 출력해야 하므로..)
	vector<int> conectedN(n + 1); // 정점 N 과 연결된 노드들 체크(0: 연결 안 됨, 1: 연결 됨)
	queue<int> q;
	q.push(n);
	conectedN[n] = 1; // 자기자신으로는 당연히 연결되어 있음..
	// 비어 있지 않을 때까지..
	while (!q.empty()) {
		int nodeNum = q.front();
		q.pop();

		for (int i = 0; i < reverseEdges[nodeNum].size(); i++) {
			int linkNode = reverseEdges[nodeNum][i]; // 지금 노드와 연결된 노드 번호 가져옴.
			if (conectedN[linkNode] == 0) {
				q.push(linkNode);
				conectedN[linkNode] = 1; // 연결 체크
			}
		}
	}

	// 민승이네 집은 1번 교차점, 코레스코 콘도는 n 번 교차점에 있음
	// 민승이네 집부터 코레스코 콘도까지 가는 동안 거치게 되는 교차점들의 번호를 차례로 출력
	vector<int> distance((n + 1), INT_MAX); // 0번 인덱스는 안 씀
	distance[1] = 0; // 민승이 집에서 민승이 집은 0..


	// 반복문을 n-1 번 돌아야함.
	for (int i = 0; i < n - 1; i++) {
		for (int j = 0; j < m; j++) {
			// 모든 간선을 다 돌면서 거리값 업데이트 해야함
			pair<pair<int, int>, int> edge = edges[j];
			int u = edge.first.first; // 시작
			int v = edge.first.second; // 끝
			int w_uv = edge.second; // 가중치

			if ((distance[u] != INT_MAX) && ((distance[u] + w_uv) < distance[v])) {
				distance[v] = distance[u] + w_uv;
				prev[v] = u; // 이전 정점 저장
			}
		}
	}

	bool minusCycle = false;
	int cycleNum = -1;
	for (int i = 0; i < m; i++) {
		// 모든 간선을 다 돌면서 거리값 업데이트 해야함
		pair<pair<int, int>, int> edge = edges[i];
		int u = edge.first.first; // 시작
		int v = edge.first.second; // 끝
		int w_uv = edge.second; // 가중치

		// 음의 사이클!
		// 이게 정점 n 이랑 연결되는지 확인!
		if ((distance[u] != INT_MAX) && ((distance[u] + w_uv) < distance[v])) {
			if (connected[u] == 1 || conectedN[v] == 1) {
				minusCycle = true;
				cycleNum = v;
			}
		}
	}


	stack<int> output;
	output.push(n); // 처음으로 n 값 넣기
	if (minusCycle) {
		if (conectedN[cycleNum])
			cout << -1 << "\n";
	}
	else {
		int start = n;
		while (prev[start] != 1) {
			output.push(prev[start]);
			start = prev[start];
		}
		output.push(1); // 마지막으로 1 넣기

		while (!output.empty()) {
			int num = output.top();
			output.pop();
			cout << num << " ";
		}
	}


	return 0;
}

 

어제 풀었던 벨만 포드 기본 문제랑은 또 다르게 응용하는 문제였다.

 

문제에서 민승이네 집에서 출발하여 코레스코 콘도에 도착하는경로 중 금품의 양이 최대가 되는 경로를 찾으라고 하였는데 지금 최대가 되는 경로를 찾는 거니까 입력으로 받는 가중치의 값에 -1 을 곱해주면 다시 최소가 되는 경로를 찾는 문제로 바뀐다. 즉 입력값의 가중치에 -1 을 곱해서 받는 방법으로 이제 다시 벨만 포드 알고리즘을 이용할 수 있다.

 

 

근데 이 문제는 어제 풀었던 문제와는 다른게 음의 사이클이 존재한다고 해서 -1 을 출력하는 것이 아니다.

출력
최적의 경로를 구할 수 있다면 민승이네 집부터 코레스코 콘도까지 가는 동안 거치게 되는 교차점들의 번호를 공백 하나를 사이에 두고 차례로 출력하면 된다. 그런데, 경우에 따라서는 최적의 경로라는 것이 존재하지 않는 상황이 발생한다. 어떠한 경우에 그런 상황이 발생하는지 생각해 보자. 그러한 경우에는 -1을 출력하도록 한다.최적의 경로가 여러 개 존재할 때는 아무거나 출력해도 된다.

 

즉, 출력 조건을 보면 음의 사이클이 존재한다고 해서 바로 -1을 출력하는 것이 아니라, 정점 n 과 음의 사이클이 연결될 때 비로소 -1 을 출력하는 것이다. 즉, 다시 말하면 음의 사이클이 존재하더라도 정점 n 과 연결되어 있지 않으면 -1 을 출력하지 않고 민승이네 집에서 코레스코 콘도로가는 경로를 출력하는 것이다.

 

 

그러면 음의 사이클이랑 해당 정점이 연결되어 있는지의 여부를 어떻게 알 수 있는가? 바로 간선을 입력 받을 때 reverse 간선도 하나 만들어서 따로 저장해놓는다. 그리고 이 배열을 이용해서 정점 n 이랑 연결되어 있는 정점들을 conected 배열에 체크해놓는다. 그래서 나중에 음의 사이클이랑 정점 n 이랑 연결되어 있는지를 판단한다.   

 

 

마지막으로는 이제 해당 정점으로 가는 경로를 출력해야 하는 것이므로 prev 배열을 이용하여 해당 정점부터 역추적하여 스택에 차곡차곡 넣고, 마지막으로 다시 스택 요소를 하나씩 빼서 출력하면 된다. 경로를 출력하는 문제는 이번이 처음이었어서 새로운 걸 알게 된 것 같아 좋았다. 조금 어렵지만 복습할 때 스스로 다시 풀어봐야겠다.

 

 

 

참고자료: 

[백준/C++] 1738번: 골목길(G2)

 

[백준/C++] 1738번: 골목길(G2)

문제 https://www.acmicpc.net/problem/1738 1738번: 골목길 첫째 줄에 골목길들이 교차하는 지점의 개수 n (2 ≤ n ≤ 100)과 골목길의 개수 m (1 ≤ m ≤ 20,000) 이 차례로 주어진다. 이어지는 m개의 행에 각각의

ezeun.tistory.com

 

'백준 문제/Bellman-Ford' 카테고리의 다른 글
  • [백준] 11657번 타임머신
dubu0721
dubu0721
dubu0721 님의 블로그 입니다.
  • dubu0721
    dubu0721 님의 블로그
    dubu0721
  • 전체
    오늘
    어제
    • 분류 전체보기 (334)
      • 프로그래밍언어론 정리 (0)
      • 컴퓨터네트워크 정리 (5)
      • 알고리즘&자료구조 공부 (64)
        • it 취업을 위한 알고리즘 문제풀이 입문 강의 (60)
        • 학교 알고리즘 수업 (3)
        • 실전프로젝트I (0)
      • 백준 문제 (193)
        • 이분탐색 (7)
        • 투포인트 (10)
        • 그래프 (7)
        • 그리디 (24)
        • DP (25)
        • BFS (15)
        • MST (7)
        • KMP (4)
        • Dijkstra (3)
        • Disjoints Set (4)
        • Bellman-Ford (2)
        • 시뮬레이션 (3)
        • 백트래킹 (15)
        • 위상정렬 (5)
        • 자료구조 (25)
        • 기하학 (1)
        • 정렬 (11)
        • 구현 (8)
        • 재귀 (8)
        • 수학 (8)
      • 유니티 공부 (11)
        • 레트로의 유니티 게임 프로그래밍 에센스 (11)
        • 유니티 스터디 자료 (0)
        • C# 공부 (0)
      • 유니티 프로젝트 (48)
        • 케이크게임 (13)
        • 점토게임 (35)
      • 언리얼 공부 (10)
        • 이득우의 언리얼 프로그래밍 (10)
      • 진로 (1)
      • 논문 읽기 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    이분탐색
    해시
    투포인터
    큐
    레트로의 유니티 프로그래밍
    골드메탈
    dp
    재귀
    수학
    C#
    유니티 프로젝트
    자료구조
    그리디
    BFS
    오블완
    스택
    백트래킹
    맵
    이득우
    언리얼
    시뮬레이션
    이벤트 트리거
    백준
    정렬
    그래프
    우선순위큐
    티스토리챌린지
    유니티 공부 정리
    유니티
    바킹독
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
dubu0721
[백준] 1738번 골목길
상단으로

티스토리툴바