백준 7569는 M × N × H 상자에서 처음부터 익은 모든 토마토를 동시에 출발점으로 삼는 3차원 BFS 문제다. 여섯 방향 탐색 자체보다 여러 시작점을 같은 0일 차 queue에 넣고, 익지 않은 토마토 수를 끝까지 추적하는 것이 핵심이다.
입력 값을 정확히 해석하기
1: 익은 토마토0: 익지 않은 토마토-1: 토마토가 들어 있지 않은 칸
원래 메모에서는 -1을 썩은 토마토라고 적었지만 문제에서 의미하는 것은 empty cell이다. M은 가로, N은 세로, H는 층 수이며 입력은 가장 아래 층부터 N줄씩 주어진다.
왜 Multi-Source BFS인가
익은 토마토가 여러 개라면 각각에서 BFS를 따로 돌리는 것이 아니다. 모든 익은 칸을 처음 queue에 넣으면 같은 distance의 칸이 함께 퍼져 나간다.
day 0: 처음 익은 모든 칸
day 1: day 0과 인접한 익지 않은 칸
day 2: day 1에서 새로 익은 칸의 이웃
각 칸을 처음 방문한 시점이 가장 빨리 익을 수 있는 날이다. unit edge graph에서 BFS가 최단 distance를 보장하기 때문이다.
Java 풀이
최대 모든 칸이 queue에 들어갈 수 있으므로, 좌표마다 new int[]를 만들지 않고 전체 칸 수 크기의 primitive int[] queue에 좌표를 encoding했다.
import java.io.BufferedInputStream;
import java.io.IOException;
public class Main {
private static int width;
private static int height;
private static int layers;
public static void main(String[] args) throws Exception {
FastScanner scanner = new FastScanner();
width = scanner.nextInt();
height = scanner.nextInt();
layers = scanner.nextInt();
int[][][] box = new int[layers][height][width];
int totalCells = width * height * layers;
int[] queue = new int[totalCells];
int head = 0;
int tail = 0;
int unripe = 0;
for (int layer = 0; layer < layers; layer++) {
for (int row = 0; row < height; row++) {
for (int column = 0; column < width; column++) {
int state = scanner.nextInt();
box[layer][row][column] = state;
if (state == 1) {
queue[tail++] = encode(layer, row, column);
} else if (state == 0) {
unripe++;
}
}
}
}
int[] dl = {1, -1, 0, 0, 0, 0};
int[] dr = {0, 0, 1, -1, 0, 0};
int[] dc = {0, 0, 0, 0, 1, -1};
int maxDayValue = 1;
while (head < tail) {
int position = queue[head++];
int column = position % width;
position /= width;
int row = position % height;
int layer = position / height;
for (int direction = 0; direction < 6; direction++) {
int nextLayer = layer + dl[direction];
int nextRow = row + dr[direction];
int nextColumn = column + dc[direction];
if (nextLayer < 0 || nextLayer >= layers
|| nextRow < 0 || nextRow >= height
|| nextColumn < 0 || nextColumn >= width) {
continue;
}
if (box[nextLayer][nextRow][nextColumn] != 0) {
continue;
}
box[nextLayer][nextRow][nextColumn] =
box[layer][row][column] + 1;
maxDayValue = Math.max(
maxDayValue,
box[nextLayer][nextRow][nextColumn]
);
unripe--;
queue[tail++] = encode(nextLayer, nextRow, nextColumn);
}
}
System.out.println(unripe == 0 ? maxDayValue - 1 : -1);
}
private static int encode(int layer, int row, int column) {
return (layer * height + row) * width + column;
}
private static final class FastScanner {
private final BufferedInputStream input =
new BufferedInputStream(System.in);
private final byte[] buffer = new byte[1 << 16];
private int index;
private int size;
int nextInt() throws IOException {
int value = 0;
int sign = 1;
int current;
do {
current = read();
} while (current <= ' ');
if (current == '-') {
sign = -1;
current = read();
}
while (current > ' ') {
value = value * 10 + current - '0';
current = read();
}
return value * sign;
}
private int read() throws IOException {
if (index >= size) {
size = input.read(buffer);
index = 0;
if (size < 0) {
return -1;
}
}
return buffer[index++];
}
}
}
box에는 원래 상태와 day를 함께 저장한다. 처음 익은 칸이 1이고 그 이웃이 2이므로 최종 answer는 최대 값에서 1을 뺀다.
원래 풀이에서 바로잡은 경계
처음부터 0인 칸이 하나도 없다면 모두 익는 데 걸리는 최소 일수는 0이다. 원래 설명의 “1을 출력”은 잘못 적은 값이었고 실제 code는 0을 출력하고 있었다.
또한 BFS가 끝났다고 모든 토마토가 익은 것은 아니다. -1 벽으로 완전히 둘러싸인 0은 queue에 들어오지 못한다. 처음 센 unripe를 새로 익힐 때마다 줄이고, 종료 후 0인지 확인하면 별도 3차원 scan 없이 판정할 수 있다.
복잡도
각 칸은 최대 한 번 queue에 들어가고 여섯 neighbor를 확인한다.
- 시간 복잡도:
O(MNH) - 공간 복잡도:
O(MNH)
2차원 BFS의 기본 흐름은 미로 탐색 Java 풀이, graph traversal의 공통 원리는 그래프와 DFS·BFS에서 이어서 볼 수 있다.
참고 자료
'배움과 성장 > 알고리즘·문제풀이' 카테고리의 다른 글
| 백준 24416 피보나치 수 1 Java: 재귀와 DP 실행 횟수 구하기 (0) | 2022.06.17 |
|---|---|
| 백준 1707 Java: 이분 그래프를 BFS 2-Coloring으로 판별하기 (0) | 2022.06.15 |
| 백준 1637 날카로운 눈 Java: 누적 개수의 홀짝과 이분 탐색 (0) | 2022.05.29 |
| 백준 12015 Java: LIS 길이를 Lower Bound로 O(N log N)에 구하기 (0) | 2022.05.29 |
| 백준 24444·24445 Java: BFS 방문 순서와 인접 리스트 정렬 (0) | 2022.05.28 |
댓글