자바칩
[백준 / Java] 16236: 아기상어 (시뮬레이션, BFS) 본문
난이도: Gold 3
문제: https://www.acmicpc.net/problem/16236
문제
N×N 크기의 공간에 물고기 M마리와 아기 상어 1마리가 있다. 공간은 1×1 크기의 정사각형 칸으로 나누어져 있다. 한 칸에는 물고기가 최대 1마리 존재한다.
아기 상어와 물고기는 모두 크기를 가지고 있고, 이 크기는 자연수이다. 가장 처음에 아기 상어의 크기는 2이고, 아기 상어는 1초에 상하좌우로 인접한 한 칸씩 이동한다.
아기 상어는 자신의 크기보다 큰 물고기가 있는 칸은 지나갈 수 없고, 나머지 칸은 모두 지나갈 수 있다. 아기 상어는 자신의 크기보다 작은 물고기만 먹을 수 있다. 따라서, 크기가 같은 물고기는 먹을 수 없지만, 그 물고기가 있는 칸은 지나갈 수 있다.
아기 상어가 어디로 이동할지 결정하는 방법은 아래와 같다.
- 더 이상 먹을 수 있는 물고기가 공간에 없다면 아기 상어는 엄마 상어에게 도움을 요청한다.
- 먹을 수 있는 물고기가 1마리라면, 그 물고기를 먹으러 간다.
- 먹을 수 있는 물고기가 1마리보다 많다면, 거리가 가장 가까운 물고기를 먹으러 간다.
- 거리는 아기 상어가 있는 칸에서 물고기가 있는 칸으로 이동할 때, 지나야하는 칸의 개수의 최솟값이다.
- 거리가 가까운 물고기가 많다면, 가장 위에 있는 물고기, 그러한 물고기가 여러마리라면, 가장 왼쪽에 있는 물고기를 먹는다.
아기 상어의 이동은 1초 걸리고, 물고기를 먹는데 걸리는 시간은 없다고 가정한다. 즉, 아기 상어가 먹을 수 있는 물고기가 있는 칸으로 이동했다면, 이동과 동시에 물고기를 먹는다. 물고기를 먹으면, 그 칸은 빈 칸이 된다.
아기 상어는 자신의 크기와 같은 수의 물고기를 먹을 때 마다 크기가 1 증가한다. 예를 들어, 크기가 2인 아기 상어는 물고기를 2마리 먹으면 크기가 3이 된다.
공간의 상태가 주어졌을 때, 아기 상어가 몇 초 동안 엄마 상어에게 도움을 요청하지 않고 물고기를 잡아먹을 수 있는지 구하는 프로그램을 작성하시오.
입력
첫째 줄에 공간의 크기 N(2 ≤ N ≤ 20)이 주어진다.
둘째 줄부터 N개의 줄에 공간의 상태가 주어진다. 공간의 상태는 0, 1, 2, 3, 4, 5, 6, 9로 이루어져 있고, 아래와 같은 의미를 가진다.
- 0: 빈 칸
- 1, 2, 3, 4, 5, 6: 칸에 있는 물고기의 크기
- 9: 아기 상어의 위치
아기 상어는 공간에 한 마리 있다.
출력
첫째 줄에 아기 상어가 엄마 상어에게 도움을 요청하지 않고 물고기를 잡아먹을 수 있는 시간을 출력한다.\
문제 이해를 잘못해서 몇시간동안 쩔쩔매다가 결국 내 힘으로 풀지 못하게 되었다.
사실 문제에 써있는 그대로 구현하면 되는건데 문제 이해를 제대로 못해서 그게 좀 어려웠다.
먼저 좌표 클래스를 정적 멤버 클래스로 다음과 같이 내부에 선언한다.
참고로 현재 칸까지 이동하는 데 걸린 시간은 현재 칸까지 이동 거리이다.
문제에 주어진 초기 2차원 배열은 이렇다.
아기상어의 기본 크기는 1이고, 먹은 물고기 수와 크기가 같아지면 크기를 1 증가시키고 먹은 물고기 수를 다시 0으로 초기화해야한다.
시간은 먹은 물고기까지 이동한 거리이다.
문제에서는 더 이상 먹을 수 있는 물고기가 배열에 없을 때까지, 즉 아기 상어의 크기보다 작은 물고기가 더 이상 배열에 없을 때까지만 수행하고 걸린 시간을 구하라고 했다.
먹을 수 있는 물고기들을 모두 잡아먹을 수 있는 시간을 구하는 메서드를 만들자.
이 메서드의 매개변수는 아기 상어의 맨 처음 x좌표, y좌표이다.
findNearestFish 메서드는 거리가 가장 가까운 물고기를 구하는 메서드이다.
이 메서드가 null을 리턴하면 먹을 수 있는 물고기가 아예 없는 것이다.
여기서 구한 물고기를 리턴하여 이 메서드의 인자로 넘어온 x, y 변수를 물고기의 x좌표, y좌표로 바꾸고,
이 메서드의 지역 변수인 time(먹을 수 있는 물고기들을 모두 잡아먹을 수 있는 시간)에 물고기까지 이동하는 데 걸린 시간을 누적시킨다.
그리고 물고기를 하나 먹었으므로 count(물고기를 먹은 개수)를 증가시키고,
물고기가 있었던 칸을 0으로 만든다.
여기서 아기 상어의 크기와 같은 수의 물고기를 먹었다면 크기를 1 증가시키고, 먹은 물고기의 개수는 다시 0으로 만든다.
이 과정을 더 이상 먹을 수 있는 물고기가 없을 때까지, 즉 findNeareastFish 메서드가 null을 리턴했다면 최종적으로 이 메서드에서 time(먹을 수 있는 물고기들을 모두 잡아먹을 수 있는 시간)을 리턴하면 된다.
거리가 가장 가까운 물고기를 구하는 메서드인 findNeareastFish 메서드의 구현은 다음과 같다.
기존 2차원 배열의 BFS 구현 방식과 비슷하지만 조금 다른 점은 우선순위 큐를 만들었다는 것이다.
그냥 큐는 상하좌우로 이동하는 데에 사용되고, 우선순위 큐는 먹을 수 있는 물고기들을 모두 담는 데에 사용된다.
우선순위 큐를 만든 이유는 문제에 어떤 물고기를 먼저 먹어야 할 지 조건을 명시해두었기 때문이다.
- 먹을 수 있는 물고기가 1마리보다 많다면, 거리가 가장 가까운 물고기를 먹으러 간다.
- 거리는 아기 상어가 있는 칸에서 물고기가 있는 칸으로 이동할 때, 지나야하는 칸의 개수의 최솟값이다.
- 거리가 가까운 물고기가 많다면, 가장 위에 있는 물고기, 그러한 물고기가 여러마리라면, 가장 왼쪽에 있는 물고기를 먹는다.
위 조건을 만족하게 만든 우선순위 큐에서 물고기를 하나 뽑아서 리턴하면
물고기들 중에 거리가 가장 가깝고, 가장 위에 있고, 가장 왼쪽에 있는 물고기를 리턴한다.
그렇다면 이제 문제에 주어진 예시를 계속 이어서 그림으로 설명해보겠다.
참고로 아기 상어가 있었던 칸(9 라고 적혀있는 칸)은 위 메서드를 수행하기 전에 0으로 만들어야 한다.
먼저 아기 상어의 크기보다 작은 물고기들(먹을 수 있는 물고기들)이 있는 칸을 찾는다.
여기에서는 먹을 수 있는 물고기들이 2개이다.
- 먹을 수 있는 물고기가 1마리보다 많다면, 거리가 가장 가까운 물고기를 먹으러 간다.
- 거리는 아기 상어가 있는 칸에서 물고기가 있는 칸으로 이동할 때, 지나야하는 칸의 개수의 최솟값이다.
- 거리가 가까운 물고기가 많다면, 가장 위에 있는 물고기, 그러한 물고기가 여러마리라면, 가장 왼쪽에 있는 물고기를 먹는다.
여기서는 아기 상어에서 물고기까지 이동 거리가 둘 다 3이므로, 더 위에 있는 물고기쪽으로 이동시켜야한다.
우선순위 큐를 만들었기 때문에 이 과정은 원활하게 작동하게 된다.
참고로 우선순위 큐의 구현을 다시 한번 보자면 이렇다.
조건에 맞게 물고기 하나를 먹었다.
물고기 수를 1 증가시키고, 아기 상어에서 물고기까지의 이동 거리는 3이다.
나머지 물고기도 하나 먹었다.
물고기 수를 1 증가시키고, 바로 이전에 물고기를 먹었던 칸에서 해당 물고기까지의 이동 거리는 6이다.
이전 시간은 3이었는데 여기에 이동 거리인 6을 더해서 최종 시간은 9가 된다.
아기 상어의 크기와 먹은 물고기의 수가같아졌으므로 크기를 1 증가시킨 3으로 만들고, 먹은 물고기의 수는 0으로 만든다.
아기 상어의 크기보다 작은 물고기들(먹을 수 있는 물고기들)이 있는 칸을 찾는다.
여기에서는 먹을 수 있는 물고기들이 2개이다.
가장 가까운 물고기는 바로 오른쪽에 있는 물고기이므로 저것부터 먹으러 가자.
조건에 맞게 물고기 하나를 먹었다.
물고기 수를 1 증가시키고, 바로 이전에 물고기를 먹었던 칸에서 해당 물고기까지의 이동 거리는 1이다.
이전 시간은 9였는데 여기에 이동 거리인 1을 더해서 최종 시간은 10이 된다.
나머지 물고기도 하나 먹었다.
물고기 수를 1 증가시키고, 바로 이전에 물고기를 먹었던 칸에서 해당 물고기까지의 이동 거리는 4이다.
이전 시간은 10이었는데 여기에 이동 거리인 4를 더해서 최종 시간은 14가 된다.
그런데 아직 아기 상어의 크기가 3이다. 배열에는 3보다 작은 물고기를 모두 먹었으므로 더 이상 존재하지 않는다.
이제 반복문을 탈출하고 먹을 수 있는 물고기들을 모두 먹는 데 걸린 시간인 14를 리턴하면 된다.
자, 그럼 이제 전체 코드를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;
public class P16236_아기상어 {
static int N; // 공간의 크기
static int[] dx = {-1, 0, 1, 0}; // 상, 하 이동
static int[] dy = {0, -1, 0, 1}; // 좌, 우 이동
static int[][] space; // 2차원 공간 배열
static class Point {
int x, y; // 현재 칸의 좌표 (x, y)
int time; // 현재 칸까지 이동하는 데 걸린 시간
Point(int x, int y, int time) {
this.x = x;
this.y = y;
this.time = time;
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine());
space = new int[N][N];
int x = -1, y = -1; // 아기 상어의 위치
for (int i = 0; i < N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for (int j = 0; j < N; j++) {
// 0: 빈 칸
// 1, 2, 3, 4, 5, 6: 칸에 있는 물고기의 크기
// 9: 아기 상어의 위치
space[i][j] = Integer.parseInt(st.nextToken());
if (space[i][j] == 9) {
x = i; // 아기 상어의 x좌표
y = j; // 아기 상어의 y좌표
// 아기 상어를 이동 시킬 예정이므로 아기 상어가 있던 칸을 비우기
space[i][j] = 0;
}
}
}
// 먹을 수 있는 물고기를 모두 잡아먹을 수 있는 시간을 출력
System.out.println(findTimeToEatFish(x, y));
}
// 먹을 수 있는 물고기를 모두 잡아먹을 수 있는 시간 구하기
public static int findTimeToEatFish(int x, int y) {
int size = 2; // 아기 상어의 크기
int count = 0; // 먹은 물고기 개수
int time = 0; // 먹을 수 있는 물고기를 모두 잡아먹을 수 있는 시간
// 먹을 수 있는 물고기가 없을 때가 오기 전까지 무한 반복
while (true) {
// 거리가 가장 가까운 물고기 (먹을 예정인 물고기)
Point fish = findNearestFish(x, y, size);
// 물고기가 없다면 더 이상 먹을 수 있는 물고기가 없으므로 반복문 탈출
if (fish == null) {
break;
}
x = fish.x; // 먹은 물고기의 x좌표
y = fish.y; // 먹은 물고기의 y좌표
time += fish.time; // 먹은 물고기까지 이동하는 데 걸린 시간을 누적시키기
count++; // 먹은 물고기 개수 1 증가
space[x][y] = 0; // 물고기를 먹었으므로 칸 비우기
// 아기 상어의 크기와 같은 수의 물고기를 먹었다면
if (size == count) {
size++; // 아기 상어의 크기 1 증가
count = 0; // 먹은 물고기 개수 초기화
}
}
return time; // 먹을 수 있는 물고기를 모두 잡아먹을 수 있는 시간 리턴
}
// 거리가 가장 가까운 물고기 찾기
public static Point findNearestFish(int x, int y, int size) {
Queue<Point> queue = new LinkedList<>(); // bfs 수행을 위한(칸을 이동시킬) 큐
boolean[][] visited = new boolean[N][N]; // 2차원 방문 체크 배열
// 먹을 수 있는 물고기들을 담는 우선순위 큐: 아래 조건을 만족하도록 설정
// 거리가 가까운 물고기가 많다면 가장 위에 있는 물고기,
// 그러한 물고기가 여러마리라면 가장 왼쪽에 있는 물고기를 먹는다.
PriorityQueue<Point> fishes = new PriorityQueue<>((p1, p2) -> {
if (p1.time == p2.time) { // 거리가 가까운 물고기가 많다면
if (p1.x == p2.x) { // 그러한 물고기가 여러마리라면
return Integer.compare(p1.y, p2.y); // 가장 왼쪽에 있는 물고기를 먹는다.
}
return Integer.compare(p1.x, p2.x); // 가장 위에 있는 물고기를 먹는다.
}
return Integer.compare(p1.time, p2.time); // 거리가 가까운 물고기를 먹는다.
});
queue.add(new Point(x, y, 0)); // 큐에 현재 칸 삽입
visited[x][y] = true; // 현재 칸을 방문 체크
while (!queue.isEmpty()) {
Point now = queue.poll(); // 현재 칸을 큐에서 뽑기
// 현재 위치 기준으로 상, 하, 좌, 우 탐색
for (int i = 0; i < 4; i++) {
int nx = now.x + dx[i]; // 인접 칸의 x좌표
int ny = now.y + dy[i]; // 인접 칸의 y좌표
// 인접 칸의 x좌표, y좌표가 유효한 인덱스가 아니라면 건너뛰기
if (nx < 0 || ny < 0 || nx >= N || ny >= N) {
continue;
}
// 인접 칸을 방문했거나, 인접 칸에 적힌 숫자가 아기 상어의 크기보다 크다면 건너뛰기
if (visited[nx][ny] || space[nx][ny] > size) {
continue;
}
// 인접 칸에 이동 시간을 1 증가시키고 칸을 이동시킬 큐에 삽입
queue.add(new Point(nx, ny, now.time + 1));
visited[nx][ny] = true; // 인접 칸을 방문 체크
// 인접 칸에 적힌 숫자가 0보다 크고, 아기 상어의 크기보다 작다면
if (space[nx][ny] > 0 && space[nx][ny] < size) {
// 인접 칸에 이동 시간을 1 증가시키고 먹을 수 있는 물고기들을 담는 우선순위 큐에 삽입
fishes.add(new Point(nx, ny, now.time + 1));
}
}
}
if (!fishes.isEmpty()) { // 먹을 수 있는 물고기들을 담는 우선순위 큐에 물고기가 있다면
return fishes.poll(); // 거리가 가장 가까운 물고기를 뽑아서 리턴
} else { // 먹을 수 있는 물고기들을 담는 우선순위 큐에 물고기가 없다면 null 리턴
return null;
}
}
}
|
cs |
'알고리즘 > 백준' 카테고리의 다른 글
[백준 / Java] 1303: 전쟁 - 전투 (BFS) (0) | 2024.07.14 |
---|---|
[백준 / Java] 10026: 적록색약 (BFS) (0) | 2024.07.11 |
[백준 / Java] 14500: 테트로미노 (구현, 백트래킹) (0) | 2024.07.06 |
[백준 / Java] 2146: 다리 만들기 (BFS) (1) | 2024.07.05 |
[백준 / Java] 21736: 헌내기는 친구가 필요해 (BFS) (0) | 2024.07.05 |