📌 문제 링크 - https://www.acmicpc.net/problem/23030
문제
쿠기는 평소 지하철 최단 경로를 탐색하여 소요 시간을 알려주는 '후다다닥' 어플을 사용 중이다. 그러나 쿠기는 걸음이 너무 느려서 '후다다닥'이 알려주는 경로를 따라가면 항상 지하철이 떠나간 빈 플랫폼을 맞이해야 했다.
허탈한 마음에 속상해하던 쿠기는 때마침 창업경진대회가 열린다는 소식을 들었다. 쿠기는 환승 시간과 출발지, 도착지를 입력하면 최단 경로로 이동했을 때 걸리는 소요 시간을 알려주는 어플을 만들어 출품하기로 결심했다.
쿠기가 사는 도시의 지하철 노선은 총 N개가 있으며 1~N까지의 번호로 노선을 구분한다. 각 노선에는 최소 1개, 최대 50개의 역이 있다. 노선에 속한 역에는 가장 왼쪽 끝에 위치한 역을 1번으로 하여 1씩 증가하는 번호가 주어진다. 역 번호가 1씩 차이나는 두 역은 인접하다고 말할 수 있다. 하나의 역에서 인접한 역사이의 거리는 일정하여 지하철을 통해 이동하는 데 모두 1만큼의 시간이 걸린다. 지하철은 양방향 모두 통행이 가능하며, 지하철의 방향을 바꿔타는 시간과 지하철을 타기 위해 대기하는 시간은 걸리지 않는다.
쿠기가 사는 도시의 지하철에는 M개의 환승역이 존재한다. 두 개의 노선이 하나의 역에서 만나는 지점을 환승역이라고 한다. 환승역에 3개 이상의 노선이 겹치는 일은 존재하지 않는다. 환승을 하는 경우 걸어서 이동해야 하므로 사용자는 각자 자신이 환승을 하는 데 걸리는 시간 T를 입력한다. 모든 환승역에서 걸어야 하는 거리는 동일하며, 모든 요청에 대해 항상 도착할 수 있다.
하지만 쿠기는 프로그래밍을 할 줄 모르기 때문에 츄르 100개를 걸고 대신 코드를 짜줄 사람을 찾고 있다.
맛있는 츄르를 얻어보자. 사용자가 환승 시간 T와 출발지, 도착지를 입력하면 최단 경로로 이동하였을 때 걸리는 소요 시간을 알려주는 프로그램을 만들어야 한다.
입력
첫째 줄에 노선의 개수 N(2 ≤ N ≤ 10)이 주어진다.
둘째 줄에 N개의 노선별로 속한 역의 개수가 차례로 주어진다. (1 ≤ 역의 수 ≤ 50)
셋째 줄에 환승역의 개수 M(1 ≤ M ≤ ⌊모든 역의 수 / 2⌋)이 주어진다.
넷째 줄부터 M개의 줄에 환승역의 정보가 주어진다.
각 줄은 네개의 양의 정수 P1, P2, Q1, Q2로 구성되며 P1번 노선의 P2역과 Q1번 노선의 Q2역이 연결된 환승역이라는 것이다. 이미 환승역으로 주어진 역은 중복되게 주어지지 않는다.
그 다음 줄에는 사용자의 요청 개수 K(1 ≤ K ≤ 1,000)가 주어진다.
이어서 K개의 줄에 걸쳐 5개의 양의 정수 T(0 ≤ T ≤ 1,000), U1, U2, V1, V2가 주어지며, 환승에 걸리는 소요 시간이 T이며 출발지는 U1번 노선의 U2역이고 도착지는 V1번 노선의 V2역이라는 뜻이다.
출력
각 사용자의 요청에 대하여 출발지에서 도착지로 가는 최단 시간을 한 줄에 하나씩 출력한다.
풀이
지하철 노선은 총 N개가 있으며 1~N까지의 번호로 노선을 구분한다. 각 노선에는 최소 1개, 최대 50개의 역이 있다.
각 역은 인접한 역으로 이동(이동 시간 1) 할 수 있으며, 환승역일 경우 환승(이동 시간 T) 할 수 있다.
역끼리 연결 정보를 구현하고 나면 나머지는 다익스트라로 풀 수 있는 문제다.
양방향 환승역 정보를 저장하기 위해 int형 배열을 사용해 준다. (한 환승역에는 두 개의 노선만 연결될 수 있으며, 세 개 이상의 노선이 만나는 일은 없다.)
// 넷째 줄부터 M개의 줄에 환승역의 정보가 주어진다.
for(int i=0; i<M; i++) {
st = new StringTokenizer(br.readLine());
// a1번 노선의 a2역과 b1번 노선의 b2역이 연결된 환승역이라는 것
int a1 = Integer.parseInt(st.nextToken());
int a2 = Integer.parseInt(st.nextToken());
int b1 = Integer.parseInt(st.nextToken());
int b2 = Integer.parseInt(st.nextToken());
// 환승역 설정 (BASE = 100)
int a = a1 * BASE + a2;
int b = b1 * BASE + b2;
transfer[a] = b;
transfer[b] = a;
}
위의 코드와 같이 a역에서는 b역을 갈 수 있고, b역에서는 a역을 갈 수 있음을 저장해 준다.
이때 효율적으로 구현하기 위해 각 역을 (노선 * 100 + 역)과 같이 노선과 역을 하나의 고유한 정수로 관리한다. 예를 들어 1번 노선의 5번 역은 105역, 2번 노선의 4번 역은 204로 표현된다. transfer[105] = 204, transfer[204] = 105와 같이 설정할 수 있다.
이후 사용자의 요청에 따라 출발역과 도착역, 환승 시간 정보를 바탕으로 다익스트라로 탐색하여 각 요청에 대한 최소 시간을 구해주면 된다.
코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
// https://www.acmicpc.net/problem/23030
public class Main_23030 {
private static final int MAX = 987654321, L = 1000, S = 50, BASE = 100;
private static final int TOTAL = L + S + 1;
private static final int[] D = {-1, 1}; // 인접한 역 이동 방향
private static int[] transfer; // 환승정보
private static int[] stationCnt; // 노선별로 속한 역의 개수
private static Request[] requests; // 사용자의 요청
public static void main(String[] args) throws IOException {
init();
sol();
}//main
private static void sol() {
StringBuilder ans = new StringBuilder();
int minTime;
// 각 사용자의 요청에 대하여 출발지에서 도착지로 가는 최단 시간을 한 줄에 하나씩 출력
for(Request request : requests) {
minTime = getMinTime(request); // 최단 시간
ans.append(minTime).append('\n');
}
System.out.print(ans);
}//sol
private static int getMinTime(Request request) {
PriorityQueue<Station> pq = new PriorityQueue<>();
int[] visited = new int[TOTAL];
int transferTime = request.time; // 환승시간
int cur = request.start; // 현재역
int end = request.end; // 도착역
int time = 0; // 소요시간
Arrays.fill(visited, MAX);
pq.offer(new Station(cur, time));
visited[cur] = time;
Station station;
while(!pq.isEmpty()) {
station = pq.poll();
cur = station.station;
time = station.time;
if(visited[cur] < time) continue;
if(cur == end) return time;
// 인접한 역 이동 (이동 시간 1)
for(int i=0; i<2; i++) {
int next = cur + D[i]; // 다음역
if(isAvailable(cur, next) && visited[next] > time + 1) {
visited[next] = time + 1;
pq.offer(new Station(next, time + 1));
}
}
// 환승역일 경우 환승 (이동 시간 transferTime)
if(transfer[cur] != 0) {
int next = transfer[cur]; // 다음역
if(visited[next] > time + transferTime) {
visited[next] = time + transferTime;
pq.offer(new Station(next, time + transferTime));
}
}
}
return visited[end];
}//getMinTime
private static boolean isAvailable(int cur, int next) {
int curLine = cur / BASE;
int nextLine = next / BASE;
int nextStation = next % BASE;
return curLine == nextLine && nextStation > 0 && nextStation <= stationCnt[nextLine];
}//isAvailable
private static void init() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 첫째 줄에 노선의 개수 N(2 ≤ N ≤ 10)이 주어진다.
int N = Integer.parseInt(br.readLine());
transfer = new int[TOTAL]; // 환승정보
stationCnt = new int[N+1]; // 노선별로 속한 역의 개수
// 둘째 줄에 N개의 노선별로 속한 역의 개수가 차례로 주어진다. (1 ≤ 역의 수 ≤ 50)
StringTokenizer st = new StringTokenizer(br.readLine());
for(int line=1; line<=N; line++) {
stationCnt[line] = Integer.parseInt(st.nextToken());
}
// 셋째 줄에 환승역의 개수 M(1 ≤ M ≤ ⌊모든 역의 수 / 2⌋)이 주어진다.
int M = Integer.parseInt(br.readLine());
// 넷째 줄부터 M개의 줄에 환승역의 정보가 주어진다.
for(int i=0; i<M; i++) {
st = new StringTokenizer(br.readLine());
// a1번 노선의 a2역과 b1번 노선의 b2역이 연결된 환승역이라는 것
int a1 = Integer.parseInt(st.nextToken());
int a2 = Integer.parseInt(st.nextToken());
int b1 = Integer.parseInt(st.nextToken());
int b2 = Integer.parseInt(st.nextToken());
// 환승역 설정
int a = a1 * BASE + a2;
int b = b1 * BASE + b2;
transfer[a] = b;
transfer[b] = a;
}
// 사용자의 요청 개수 K(1 ≤ K ≤ 1,000)가 주어진다.
int K = Integer.parseInt(br.readLine());
requests = new Request[K];
// K개의 줄에 걸쳐 5개의 양의 정수 T(0 ≤ T ≤ 1,000), U1, U2, V1, V2가 주어짐
for(int i=0; i<K; i++) {
st = new StringTokenizer(br.readLine());
// 환승 소요 시간 T, 출발지는 U1번 노선의 U2역이고 도착지는 V1번 노선의 V2역
int t = Integer.parseInt(st.nextToken());
int u1 = Integer.parseInt(st.nextToken());
int u2 = Integer.parseInt(st.nextToken());
int v1 = Integer.parseInt(st.nextToken());
int v2 = Integer.parseInt(st.nextToken());
requests[i] = new Request(t, u1 * BASE + u2, v1 * BASE + v2);
}
br.close();
}//init
private static class Station implements Comparable<Station>{
int station; // 현재역
int time; // 소요시간
Station(int station, int time) {
this.station = station;
this.time = time;
}
@Override
public int compareTo(Station s) {
return this.time - s.time;
}
}//Station
private static class Request {
int time; // 환승에 걸리는 소요 시간
int start; // 출발역
int end; // 도착역
Request(int time, int start, int end) {
this.time = time;
this.start = start;
this.end = end;
}
}//Request
}//class
'Algorithm > 백준' 카테고리의 다른 글
[Java] 백준 18119. 단어 암기 (1) | 2025.07.22 |
---|---|
[Java] 백준 28107. 회전초밥 (0) | 2025.06.10 |
[Java] 백준 15990. 1, 2, 3 더하기 5 (0) | 2025.06.01 |
[Java] 백준 10713. 기차 여행 (0) | 2025.05.27 |
[Java] 백준 3687. 성냥개비 (1) | 2025.04.23 |