BOJ 1865 - 웜홀
핵심 아이디어
이 문제는 음수 사이클 존재 여부를 판별하는 문제다.
- 일반 도로: 양방향, 양수 가중치 (시간 증가)
- 웜홀: 단방향, 음수 가중치 (시간 감소)
출발점으로 돌아왔을 때 시간이 줄어드는 경우 = 음수 사이클 존재 → YES
알고리즘 선택: 벨만-포드
| 알고리즘 | 출발점 | 음수 간선 | 시간복잡도 | |---|---|---|---| | 다익스트라 | 단일 출발 | ❌ 불가 | O((V+E) log V) | | 벨만-포드 | 단일 출발 | ✅ 가능 | O(VE) | | 플로이드-워셜 | 모든 쌍 | ✅ 가능 | O(V³) |
음수 간선이 존재하므로 다익스트라 불가 → 벨만-포드 사용
벨만-포드 원리
Relaxation: 모든 간선에 대해 거리 갱신을 반복
dist[도착] > dist[출발] + 비용 이면 갱신
핵심: 노드가 N개일 때 최단경로는 최대 N-1개의 간선을 거친다 → relaxation을 N-1번 반복하면 모든 최단거리 확정
음수 사이클 탐지: N-1번 반복 후 한 번 더 순회했을 때 갱신이 일어나면 음수 사이클 존재 → 이미 확정됐어야 할 거리가 줄어든다는 것 = 무한히 줄어드는 사이클 존재
주요 포인트
1. 시작점을 따로 두지 않는다
그래프가 여러 연결 요소로 나뉠 수 있어서, 특정 시작점(예: 1번 노드)에서 출발하면 닿지 않는 연결 요소의 음수 사이클을 탐지하지 못한다.
모든 노드의 distance를 INF로 초기화하면 어느 연결 요소에 있든 음수 사이클이 있으면 탐지 가능하다.
대신 이 방식은 "특정 출발점에서의 최단거리"가 아니라 음수 사이클 존재 여부만 판별할 때만 유효하다.
2. 도로는 양방향, 웜홀은 단방향
// 도로: 양방향
edges.push_back({S, E, T});
edges.push_back({E, S, T});
// 웜홀: 단방향, 음수
edges.push_back({S, E, -T});
3. 간선 리스트 구조
벨만-포드는 "모든 간선을 순회"하는 게 핵심이라 인접 행렬보다 간선 리스트가 자연스럽다.
tuple<int,int,int>으로 {출발, 도착, 비용} 저장.
최종 코드
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main()
{
int TC;
const int INF = 1e9;
cin >> TC;
while(TC--){
int N, M, W;
cin >> N >> M >> W;
vector<int> distance(N+1, INF);
vector<tuple<int, int, int>> edges;
for(int i = 0; i < M; i++){
int S, E, T;
cin >> S >> E >> T;
edges.push_back({S, E, T});
edges.push_back({E, S, T});
}
for(int i = 0; i < W; i++){
int S, E, T;
cin >> S >> E >> T;
edges.push_back({S, E, -T});
}
for(int i = 0; i < N-1; i++){
for(const auto &edge : edges){
auto [S, E, T] = edge;
if(distance[E] > distance[S] + T){
distance[E] = distance[S] + T;
}
}
}
bool hasNegativeCycle = false;
for(const auto &edge : edges){
auto [S, E, T] = edge;
if(distance[E] > distance[S] + T){
hasNegativeCycle = true;
}
}
if(hasNegativeCycle) cout << "YES" << '\n';
else cout << "NO" << '\n';
}
return 0;
}