24042번 - 횡단보도
문제 풀이 시간 : 1시간
문제 요약
- N개의 지역(1번부터 N번까지)이 있고, 이들을 잇는 M개의 횡단보도가 있음
- 각 횡단보도는 1분간 파란불이 들어오고, M분 주기로 반복됨
- i번째 입력은 i, i+M, i+2M, ... 분에 해당 횡단보도에 파란불이 들어옴
- 사람이 횡단보도를 건널 수 있는 시간은 오직 파란불이 들어온 순간부터 1분 간뿐임
- 시작 지점은 1번 지역, 도착 지점은 N번 지역
- 0분부터 출발하여 최소 시간으로 도착해야 함
문제 풀이
이 문제는 보자마자 다익스트라가 떠오르는 문제였다.
하지만 일반적인 다익스트라와는 다르게 간선을 특정 시간에만 사용할 수 있기 때문에 간선을 사용할 수 있는 조건을 만족하도록 대기 시간을 적절하게 계산해야 한다.
횡단보도 연결 정보
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int u = Integer.parseInt(st.nextToken());
int v = Integer.parseInt(st.nextToken());
graph.get(u).add(new Node(v, i));
graph.get(v).add(new Node(u, i));
}i번째 입력의 횡단보도에 대해 파란불이 들어오는 시간은 i+k*M 이다. (k는 임의의 정수)
따라서 i를 해당 횡단보도의 파란불 시작 시간으로 저장해두었다.
최소 시간 계산(다익스트라 + 대기 시간)
public static void find() {
PriorityQueue<State> pq = new PriorityQueue<>(Comparator.comparingLong(s -> s.time));
pq.offer(new State(1, 0)); // 시작 지역 1번, 시간 0
dist[1] = 0;
while (!pq.isEmpty()) {
State now = pq.poll();
if (now.time > dist[now.index]) continue;
for (Node next : graph.get(now.index)) {
long nextTime;
// 현재 시간이 신호 시간보다 이르면 기다릴 필요 없음
if (now.time <= next.signalTime) {
nextTime = next.signalTime + 1; //이동하는 시간 1분을 더함
} else {
// 기다려야 하는 경우: 다음 파란불 시간을 계산
long wait = ((now.time - next.signalTime + M - 1) / M) * M;
nextTime = wait + next.signalTime + 1; //이동 시간 1분 더함
}
// 더 짧은 경로를 발견하면 갱신
if (nextTime < dist[next.to]) {
dist[next.to] = nextTime;
pq.offer(new State(next.to, nextTime));
}
}
}
}
신호가 들어오는 다음 시간을 (현재 시간 - 신호 시작 시간) / M 을 통해 계산한다.
전체 코드
import java.io.*;
import java.util.*;
class Main {
public static int n, m;
public static List<List<Node>> graph;
public static long[] dist;
static class Node {
int to; //도착 노드 번호
int signalTime; // 횡단 보도에 처음 파란불이 들어오는 시간
public Node(int to, int signalTime) {
this.to = to;
this.signalTime = signalTime;
}
}
static class State {
int index; //현재 번호
long time; //현재까지 걸린 시간
public State(int index, long time) {
this.index = index;
this.time = time;
}
}
public static void find() {
PriorityQueue<State> pq = new PriorityQueue<>(Comparator.comparingLong(s -> s.time));
pq.offer(new State(1, 0));
dist[1] = 0;
while (!pq.isEmpty()) {
State now = pq.poll();
// 이미 더 짧은 경로로 방문되었다면 패스
if (now.time > dist[now.index]) continue;
// 현재 노드에서 이동 가능한 모든 이웃 체크
for (Node next : graph.get(now.index)) {
long nextTime;
// 현재 시간이 signalTime 이전이라면 다음 파란불 시각 이전이라 해당 시간까지 기다렸다 출발
if (now.time <= next.signalTime) {
nextTime = next.signalTime + 1; //이동 시간 1 더해줌
} else { //현재 시간이 signalTime 이후라면 파란불이 지났으므로 다음 파란불을 기다려야 함
long wait = ((now.time - next.signalTime + m - 1) / m) * m; // 다음 파란불까지의 시간 계산
nextTime = wait + next.signalTime + 1; //이동 시간 계산
}
//더 짧은 시간으로 업데이트
if (dist[next.to] > nextTime) {
dist[next.to] = nextTime;
pq.offer(new State(next.to, nextTime));
}
}
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
n = Integer.parseInt(st.nextToken());
m = Integer.parseInt(st.nextToken());
graph = new ArrayList<>();
for (int i = 0; i <= n; i++) {
graph.add(new ArrayList<>());
}
for (int i = 0; i < m; i++) {
st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
graph.get(a).add(new Node(b, i));
graph.get(b).add(new Node(a, i));
}
dist = new long[n + 1];
Arrays.fill(dist, Long.MAX_VALUE);
find();
System.out.println(dist[n]);
}
}
함께 읽으면 좋은 글
5430번 - AC
함수 R은 배열을 뒤집는 연산 함수 D는 첫 번째 원소를 버리는 연산 단, 배열이 비어 있을 때 D를 수행하면 error 주어진 함수 문자열을 순서대로 수행한 뒤 최종 배열을 출력하는 문제 처음에는 문제 설명 그대로 R이 나올 때마다 배열을 실제로 뒤집고, D가 나오면 맨 앞…
1966번 - 프린터 큐
여러 문서가 큐에 들어 있고, 각 문서마다 중요도가 있음 맨 앞 문서를 확인했을 때, 더 높은 중요도의 문서가 뒤에 하나라도 있으면 맨 뒤로 보냄 그렇지 않으면 바로 인쇄 특정 위치의 문서가 몇 번째로 인쇄되는지 구하는 문제 이 문제는 프린터의 동작을 그대로 구현하면 되는…
1158 - 요세푸스 문제
1번부터 N번까지 사람이 원형으로 앉아 있음 순서대로 K번째 사람을 제거 모든 사람이 제거될 때까지 반복 제거되는 순서를 출력하는 문제 이 문제는 원형으로 순회하면서 K번째를 제거하는 과정을 그대로 구현하면 된다.
2164번 - 카드2
1부터 N까지 카드가 순서대로 쌓여 있음 맨 위 카드를 버리고, 그다음 맨 위 카드를 맨 아래로 옮기는 과정을 반복 마지막에 남는 카드 번호를 구하는 문제 이 문제는 규칙을 찾기보다, 문제에서 하라는 과정을 그대로 구현하면 된다. 카드 더미에서 반복되는 동작은 두 가지다.