IT recording...

[BOJ] 16236 아기상어 - Java 본문

Algorithm

[BOJ] 16236 아기상어 - Java

I-one 2022. 4. 24. 23:38

https://www.acmicpc.net/problem/16236

 

16236번: 아기 상어

N×N 크기의 공간에 물고기 M마리와 아기 상어 1마리가 있다. 공간은 1×1 크기의 정사각형 칸으로 나누어져 있다. 한 칸에는 물고기가 최대 1마리 존재한다. 아기 상어와 물고기는 모두 크기를 가

www.acmicpc.net

[풀이]

문제에 조건이 많고, 어떻게 풀이해야 할지 감이 잘 안와서 20분 넘게 펜으로 고민해본 문제이다.

문제를 보고 상어가 이동하면서 체크해야 할 것은 "가장 가까운 거리에 위치하는 물고기 파악" 이었다.

근데 이를 어떻게 판단할 것인가? 문제에서 상어는 못 가는 곳도 있어서 운이 나쁘다면 상어는 삥 돌아가야 하는 경우가 존재할 것이다.

최단거리는 그럼 어떻게 찾을 수 있을까?

 

1. 처음에는 dfs로 생각했다. (아무생각없이 dfs를 택했다.) 근데 코드를 열심히 짜고나니, 방문배열을 이용하는 로직이 좀 애매했다.

A -> B -> C -> D 를 가는 길이 있고 A -> E -> D 를 가는 길이 있다고 하자. ABCD를 먼저 방문한 후 AED를 방문한다면 D는 최단거리를 보장받지 못한다.

 

2. 그러다가 생각한 것이 지금 얘는 한 점에서 다른 모든 점까지의 최단 거리를 구해야 한다는 것이었다. 

갔던 길을 다시 돌아오는 경로는 존재하지 않고, 쭉쭉 뻗어나가기만 한다.

=> 그렇다면! BFS를 사용할 수 있을 것이라는 생각이 들었다.

 BFS는 visit 배열을 하나 두고 한 점에서 쭉쭉 뻗어나간다. 큐에 저장하는 자료형에 count변수를 둔다면 얘가 몇 번째 뻗어나간 애인지도 제어할 수 있다.

 

3. 먹을 수 있는 물고기는 거리가 가장 가까운 애, row가 작은 애, col이 작은 애 순으로 정해진다. Fish라는 자료형을 두고 PriorityQueue를 이용했다.

public static class Fish implements Comparable<Fish>{
    int row;
    int col;
    int count;

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

    //거리가 가까운 순, 거리가 가까운 물고기가 많다면 가장 위에 있는 물고기, 그러한 물고기가 여러마리라면 가장 왼쪽에 있는 물고기
    @Override
    public int compareTo(Fish o) {
        int cmp = Integer.compare(count,o.count);
        if(cmp==0){
            cmp = Integer.compare(row,o.row);
            if(cmp==0){
                cmp = Integer.compare(col,o.col);
            }
        }
        return cmp;
    }
}

 

4. (독해력이 딸렸던 부분) 아기 상어는 자신의 크기와 같은 수의 물고기를 먹을 때 마다 크기가 1 증가한다. 예를 들어, 크기가 2인 아기 상어는 물고기를 2마리 먹으면 크기가 3이 된다.

-> 아뿔싸 처음 문제 읽을 때 대충 읽고 당연히 물고기 먹으면 그 물고기 사이즈만큼 흡수하는 걸로 이해했다. (그래서 예제 3 답이 계속 12가 나옴) 손으로 해도 12가 나오길래 아 딴거 잘못했나보다하고 처음부터 조건 다 살펴봤다.

-> 이 조건 발견. 근데 또또 잘못 이해함 (물고기 하나 먹을 때마다 크기가 1증가한다로..) 또 답이 12가 계속 나오더라

-> 세번째 다시 읽었을 때 깨달았다.

=> 상어는 '자기 크기랑 같은 수'의 물고기를 먹을 때마다 1씩 증가한다.

 

상어 크기가 2일 때 물고기를 2번 먹으면 -> 3이 된다. 

상어 크기가 3일 때 물고기를 3번 먹으면 -> 4가 된다.

=> 먹은 물고기의 개수 % 상어 크기 == 0 이면 크기를 1씩 증가시켜주자.

(단, 먹은 물고기의 개수는 상어의 크기가 증가할 때마다 0으로 초기화해줌)

ex) 상어크기 2 , 물고기 1번 -> 상어크기 2, 물고기 2번 (2%2==0) => 3으로 UPGRADE , 물고기 0번으로 설정
상어크기 3, 물고기 1번 -> 상어크기 3, 물고기 2번 -> 상어크기 3, 물고기 3번 (3%3==0) => 4로 UPGRADE, 물고기 0번으로 설정 ..

eatFishNum++;
//아기상어는 자신의 크기와 같은 수의 물고기를 먹을 때마다 크기가 1 증가한다.
int Size = shark.size;
if(eatFishNum % shark.size==0){
    Size = shark.size+1;
    eatFishNum = 0;
}

5. 또 기타 주의해야할 조건은 더이상 먹을 수 있는 물고기가 없으면 엄마찬스 써야 한다는 것 정도?

//물고기 잡을 수 있는지 확인
if(queue.isEmpty()){
    break; //더이상 먹을 수 있는 물고기가 없다면 엄마찬스
}

 

[코드]

import java.io.*;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;

public class Boj16236_아기상어 {
    static StringTokenizer st;
    static int N;
    static int[][] Map;
    static Shark shark;
    static int answer;
    static int[] dRow = {-1,1,0,0};
    static int[] dCol = {0,0,-1,1};
    static boolean[][] visit;
    static int fishNum = 0;
    static int eatFishNum = 0;

    public static void main(String[] args) throws Exception{
        System.setIn(new FileInputStream("src/main/java/input.txt"));
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        N = Integer.parseInt(br.readLine());
        Map = new int[N+1][N+1];

        for(int i=1;i<N+1;i++){
            st = new StringTokenizer(br.readLine());
            for(int j=1;j<N+1;j++){
                Map[i][j] = Integer.parseInt(st.nextToken());
                if(1<=Map[i][j] && Map[i][j]<=6){
                    fishNum++; //총 물고기 개수
                }
                if(Map[i][j] == 9){
                    shark = new Shark(i,j,2); //상어 정보 저장
                    Map[i][j] = 0; //상어 정보는 저장했으므로 9는 이제 빈칸으로 취급해야 함
                }
            }
        }


        while(true){
            if(fishNum<=0){ //물고기 다 먹으면 끝
                break;
            }

            //상어 이동시킬때마다 초기화
            PriorityQueue<Fish> queue = new PriorityQueue<>(); //상어가 먹을 수 있는 물고기 후보들 -> 우선순위큐 사용
            visit = new boolean[N+1][N+1]; //상어가 방문한 좌표 관리
            Queue<Shark> move = new LinkedList<>(); // 상어가 이동하는 좌표(bfs돌리면서 여기다 담을거임)
            move.add(shark); //상어가 처음 있는 곳
            visit[shark.row][shark.col] = true; //상어가 처음 있는 곳 방문처리

            //bfs 시작
            while(!move.isEmpty()){
                Shark now = move.poll();
                for(int i=0;i<4;i++) {
                    int newRow = now.row + dRow[i];
                    int newCol = now.col + dCol[i];

                    if (0 < newRow && newRow <= N && 0 < newCol && newCol <= N) { //범위 확인
                        if (!visit[newRow][newCol]) { //방문할 수 있다면
                            visit[newRow][newCol] = true; //방문처리
                            if (Map[newRow][newCol] <= shark.size) { //상어가 갈 수 있냐? //0,같거나 작은 곳
                                if (Map[newRow][newCol] < shark.size && Map[newRow][newCol] != 0) { //먹을수 있는 물고기냐? //작은 곳
                                    queue.add(new Fish(newRow, newCol, now.count+1)); //물고기 후보에 추가
                                }
                                move.add(new Shark(newRow, newCol, now.size, now.count + 1)); //0,같거나 작은 곳에서 모두 가야하므로 move에 좌표 추가
                            }
                        }
                    }
                }
            }//bfs 끝

            //물고기 잡을 수 있는지 확인
            if(queue.isEmpty()){
                break; //더이상 먹을 수 있는 물고기가 없다면 엄마찬스
            }
            Fish eat = queue.poll();
            eatFishNum++;
            //아기상어는 자신의 크기와 같은 수의 물고기를 먹을 때마다 크기가 1 증가한다.
            int Size = shark.size;
            if(eatFishNum % shark.size==0){
                Size = shark.size+1;
                eatFishNum = 0;
            }

            shark = new Shark(eat.row,eat.col,Size); //상어는 물고기를 먹은 자리에 가서 서있어야 함

            Map[eat.row][eat.col] = 0; //물고기는 먹혔으므로 0 설정
            fishNum--;

            answer += eat.count; //상어가 이동한 거리는 먹힌 물고기한테 간 거리이기 때문
        }

        System.out.println(answer);
    }

    public static class Shark{
        int row;
        int col;
        int size;
        int count;

        public Shark(int row, int col, int size) {
            this.row = row;
            this.col = col;
            this.size = size;
        }

        public Shark(int row, int col, int size, int count) {
            this.row = row;
            this.col = col;
            this.size = size;
            this.count = count;
        }
    }

    public static class Fish implements Comparable<Fish>{
        int row;
        int col;
        int count;

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

        //거리가 가까운 순, 거리가 가까운 물고기가 많다면 가장 위에 있는 물고기, 그러한 물고기가 여러마리라면 가장 왼쪽에 있는 물고기
        @Override
        public int compareTo(Fish o) {
            int cmp = Integer.compare(count,o.count);
            if(cmp==0){
                cmp = Integer.compare(row,o.row);
                if(cmp==0){
                    cmp = Integer.compare(col,o.col);
                }
            }
            return cmp;
        }
    }
}

[느낀점]

소요시간 : 1h 50min

생각보다 문제를 읽는데 시간이 많이 걸렸다. 그리고 dfs에서 bfs로 생각을 바꾸는데까지도 꽤 많은 시간이 걸렸다. 

추가로 문제 조건 잘못읽어서 한 번에 풀 것을 못풀었다.. 문제 조건 더 꼼꼼히 읽자ㅜㅜ

그리고 bfs쓰긴 했지만, dfs로 짤 때 방문처리 제대로 안했었는데 dfs문제 풀 때 좀 더 주의하자

 

[시간복잡도]

1억에 1초가 걸린다고 한다. 

BFS의 시간복잡도는 O(n^2)이다. (모든 정점 n^2, 정점마다 4방 -> O(4n^2)=O(n^2))

제일 바깥의 while문은 물고기 최대 개수(n^2)만큼 실행되기 때문에 O(n^2)이다. 

=> O(n^4)

n은 20이기 때문에 삽가능이다. (16만 < 1억)

'Algorithm' 카테고리의 다른 글

[BOJ] 17837 새로운게임2 - Java  (0) 2022.04.26
[BOJ] 17779 게리맨더링 2 -Java  (0) 2022.04.25
[BOJ] 17142 연구소3 - Java  (0) 2022.04.23
[BOJ] 17140 이차원배열과연산 - Java  (0) 2022.04.23
[BOJ] 17143 낚시왕 - Java  (0) 2022.04.22
Comments