BOJ 16236 아기 상어 (Java)

사람·2025년 7월 14일
0

BOJ

목록 보기
52/76

문제

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: 아기 상어의 위치
아기 상어는 공간에 한 마리 있다.

출력
첫째 줄에 아기 상어가 엄마 상어에게 도움을 요청하지 않고 물고기를 잡아먹을 수 있는 시간을 출력한다.

예제 입력 1
3
0 0 0
0 0 0
0 9 0
예제 출력 1
0

예제 입력 2
3
0 0 1
0 0 0
0 9 0
예제 출력 2
3

예제 입력 3
4
4 3 2 1
0 0 0 0
0 0 9 0
1 2 3 4
예제 출력 3
14

예제 입력 4
6
5 4 3 2 3 4
4 3 2 3 4 5
3 2 9 5 6 6
2 1 2 3 4 5
3 2 1 6 5 4
6 6 6 6 6 6
예제 출력 4
60

예제 입력 5
6
6 0 6 0 6 1
0 0 0 0 0 2
2 3 4 5 6 6
0 0 0 0 0 2
0 2 0 0 0 0
3 9 3 0 0 1
예제 출력 5
48

예제 입력 6
6
1 1 1 1 1 1
2 2 6 2 2 3
2 2 5 2 2 3
2 2 2 4 6 3
0 0 0 0 0 6
0 0 0 0 0 9
예제 출력 6
39

접근

워낙 유명한 문제인 것으로 알고 있다.
이런 빡구현 문제 진짜 오랜만에 풀어본다...^^ 어려웠다기보다는 고려해야 될 게 너무 많아서 헷갈렸다.

일단 기본적으로 BFS 문제이다. 왜냐면 현재 상어의 위치에서 거리가 가장 가까운 물고기를 찾기 위해서는 거리를 1씩 늘려 가며 전체 너비를 탐색해야 하기 때문이다. 그렇게 해서 먹을 수 있는 물고기를 찾았다면, 해당 위치로 상어를 이동시켜서 먹고. 더 이상 먹을 수 있는 물고기가 없을 때까지 이걸 반복하면 된다.

가장 헷갈렸던 부분이 '거리가 가까운 물고기가 많다면, 가장 위에 있는 물고기, 그러한 물고기가 여러마리라면, 가장 왼쪽에 있는 물고기를 먹는다.'는 조건이었다. 처음에는 BFS 탐색 순서를 왼쪽 위부터 하면 되겠다~ 하고 막연하게 생각했는데, 좀 더 고민해보니 탐색 순서를 그렇게 하더라도 '가장' 왼쪽 위에 있는 물고기가 먼저 탐색되지는 않겠더라. 오히려 가장 왼쪽 위까지 들어가 탐색을 해야 하기 때문에 거리가 가장 가까운 물고기들 사이에서 가장 왼쪽 위에 있는 걸 찾는 건 DFS에 더 가깝겠다는 생각이 들었다.
물론 DFS 자체를 실제로 하지는 않았고, 거리가 가장 가까운 물고기들을 모두 우선순위 큐에 넣어서 poll 했을 때 가장 왼쪽 위에 있는 물고기가 맨 처음 나오도록 구현을 했다.

1시간 정도 걸려서 한 번에 맞히긴 했는데 실제 코테에 이런 문제가 나오면 식은땀 나서 못 풀 것 같다;;..

구현

import java.io.*;
import java.util.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());
        int[][] space = new int[N][N];
        int[] curr = null;
        
        for (int i = 0; i < N; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            for (int j = 0; j < N; j++) {
                space[i][j] = Integer.parseInt(st.nextToken());
                if (space[i][j] == 9) {
                    curr = new int[] {i, j};
                }
            }
        }

        int time = 0;
        int eatingCount = 0;
        int sharkSize = 2;
        int[] dx = {-1, 0, 1, 0};
        int[] dy = {0, -1, 0, 1};
        while (true) { 
            Queue<Fish> q = new LinkedList<>();
            boolean[][] visited = new boolean[N][N];
            q.offer(new Fish(curr[0], curr[1], 0));
            visited[curr[0]][curr[1]] = true;
            PriorityQueue<Fish> closestFishes = new PriorityQueue<>((f1, f2) -> (f1.row == f2.row)? f1.col - f2.col : f1.row - f2.row);
            int minDistance = 0;

            while (!q.isEmpty()) {
                Fish fish = q.poll();
                if (minDistance > 0 && minDistance != fish.distance + 1) {
                    break;
                }
                for (int i = 0; i < 4; i++) {
                    int nextRow = dx[i] + fish.row;
                    int nextCol = dy[i] + fish.col;
                    if (nextRow >= 0 && nextRow < N && nextCol >= 0 && nextCol < N) {
                        // 아기 상어보다 큰 물고기가 있어 지나갈 수 없음 or 이미 지나왔던 위치
                        if ((space[nextRow][nextCol] > sharkSize) || visited[nextRow][nextCol]) {
                            continue;
                        }
                        
                        // 먹을 수 있는 물고기 발견
                        if (space[nextRow][nextCol] > 0 && space[nextRow][nextCol] < sharkSize) {
                            closestFishes.offer(new Fish(nextRow, nextCol, fish.distance + 1));
                            minDistance = fish.distance + 1;
                        } else { // 빈공간이거나 먹을 수 없는 물고기 -> 그냥 지나가기
                            q.offer(new Fish(nextRow, nextCol, fish.distance + 1));
                        }
                        visited[nextRow][nextCol] = true;
                    }
                }
            }

            if (closestFishes.isEmpty()) {
                System.out.println(time);
                return;
            }

            Fish eatenFish = closestFishes.poll();
            space[curr[0]][curr[1]] = 0;
            space[eatenFish.row][eatenFish.col] = 9;
            curr[0] = eatenFish.row;
            curr[1] = eatenFish.col;
            eatingCount++;
            if (eatingCount == sharkSize) {
                eatingCount = 0;
                sharkSize++;
            }
            time += eatenFish.distance;
        }
    }

    static class Fish {
        int row;
        int col;
        int distance;

        Fish(int row, int col, int distance) {
            this.row = row;
            this.col = col;
            this.distance = distance;
        }
    }
}

profile
알고리즘 블로그 아닙니다.

0개의 댓글