알고리즘/문제풀이

[백준/BOJ] 20058 마법사 상어와 파이어스톰 - JAVA

LIRI 2025. 7. 1. 20:20

[백준/BOJ] 20058 마법사 상어와 파이어스톰 - JAVA


문제

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

 

문제 분석

조건

`(2N)×(2N)` 크기의 격자에 각 칸의 얼음의 양이 주어진다. Q번의 파이어스톰을 시전할때 한번의 파이어스톰은 아래 과정을 거친다.

1. 전체 격자를 `(2L)×(2L)`크기의 부분격자로 나누고 모든 부분격자는 시계 방향으로 90도 회전한다.

2. 상하좌우로 인접한 칸에 얼음이 있는 칸이 3개 미만이면 해당 칸의 얼음의 양이 1 줄어든다.

Q번의 파이어스톰을 모두 시전한 후, 전체 격자에 남아있는 얼음의 총량가장 큰 얼음 덩어리가 차지하는 칸의 개수를 출력하는 문제이다.

풀이방법

단순 구현문제로 부분적으로 격자를 회전시키고 얼음을 녹이는 것을 구현하였다. 남은 얼음 양의 합을 따로 구하지 않고, 격자를 입력받을 때 총 얼음의 양을 구하였고, 얼음의 양이 줄때마다 총 얼음의 양을 같이 감소시켰다.

얼음의 양을 감소 시키는 부분에서는 격자를 탐색하며 바로 양을 줄이는 것이 아니라 좌표를 리스트에 넣고 양을 줄이도록 하였다. 그 이유는 아래처럼 격자칸에 얼음이 남아있을때 좌표를 하나씩 탐색하며 감소시킬 경우, 이미 녹아 버린 값을 참조하여 감소하면 안되는 좌표가 감소하게 될 수도 있기때문이다.

아래처럼 2번이 들어있는 격자칸은 얼음이 감소하면 안되는 칸이지만 순차적으로 확인 후 바로 감소를 진행하게 된다면 오른쪽처럼 감소된 후의 값을 참고하게 되어 얼음양이 감소할 수 있다.

X 1 X      x 0 x
1 2 1 ---> 0 1
X 5 6

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {
    static int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    static int[][] map;
    static int mapSize;
    static int bigIceSize = 0;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int remainIce = 0;

        int N = Integer.parseInt(st.nextToken());
        int Q = Integer.parseInt(st.nextToken());

        mapSize = (int) Math.pow(2, N);
        map = new int[mapSize][mapSize];
        for (int r = 0; r < mapSize; r++) {
            st = new StringTokenizer(br.readLine());
            for (int c = 0; c < mapSize; c++) {
                map[r][c] = Integer.parseInt(st.nextToken());
                remainIce += map[r][c]; // 초기 얼음 양 계산
            }
        }

        st = new StringTokenizer(br.readLine());
        for (int q = 0; q < Q; q++) {
            // 격자 부분 회전
            int L = Integer.parseInt(st.nextToken());
            int splitSize = (int) Math.pow(2, L);
            for (int r = 0; r < mapSize; r += splitSize) {
                for (int c = 0; c < mapSize; c += splitSize) {
                    rotateArea(r, c, splitSize);
                }
            }

            // ice--
            List<int[]> declineList = new ArrayList<>();
            for (int r = 0; r < mapSize; r++) {
                for (int c = 0; c < mapSize; c++) {
                    if (map[r][c] == 0) {
                        continue;
                    }
                    if (isDecline(r, c)) {  // 주위 얼음이 있는 칸이 3칸 미만인지 확인
                        declineList.add(new int[]{r, c});   // 미리 감소시키면 안됨
                    }
                }
            }
            for (int[] pos : declineList) { // 얼음의 양은 동시 감소
                map[pos[0]][pos[1]]--;
                remainIce--;    // 남은 얼음 양 계산
            }
        }

        // 얼음 덩어리 크기 계산
        for (int r = 0; r < mapSize; r++) {
            for (int c = 0; c < mapSize; c++) {
                if (map[r][c] > 0) {
                    bigIceSize = Math.max(bigIceSize, bfs(r, c));
                }
            }
        }

        System.out.println(remainIce);
        System.out.println(bigIceSize);
    }

    /**
     * 구역의 크기 계산
     *
     * @return 얼음 덩어리 크기
     */
    private static int bfs(int r, int c) {
        int areaCount = 0;
        map[r][c] = 0;
        Queue<int[]> queue = new ArrayDeque<>();
        queue.add(new int[]{r, c});
        while (!queue.isEmpty()) {
            areaCount++;
            int[] now = queue.poll();
            for (int[] dir : dirs) {
                int nextR = now[0] + dir[0];
                int nextC = now[1] + dir[1];
                if (isInvalid(nextR, nextC)) {
                    continue;
                }
                if (map[nextR][nextC] > 0) {
                    queue.add(new int[]{nextR, nextC});
                    map[nextR][nextC] = 0;
                }
            }
        }
        return areaCount;
    }

    /**
     * 얼음이 감소하는지 확인
     *
     * @return 감소해야되면 true
     */
    private static boolean isDecline(int r, int c) {
        int noIceCount = 0; // 얼음이 없는 인접구역 카운트
        for (int[] dir : dirs) {
            if (noIceCount > 1) {  // 인접한 곳에 얼음이 없는 칸이 2칸 이상이면 감소함
                return true;
            }
            int newR = r + dir[0];
            int newC = c + dir[1];
            if (isInvalid(newR, newC)) { // 격자 밖도 없음이 없음
                noIceCount++;
                continue;
            }
            if (map[newR][newC] == 0) {
                noIceCount++;
            }
        }
        return noIceCount > 1;
    }

    private static boolean isInvalid(int r, int c) {
        return r < 0 || c < 0 || r >= mapSize || c >= mapSize;
    }

    /**
     * 격자 회전
     *
     * @param sr        격자 시작 좌표
     * @param sc
     * @param splitSize 격자 크기
     */
    private static void rotateArea(int sr, int sc, int splitSize) {
        int[][] temp = new int[splitSize][splitSize];
        // 회전 후 임시 배열에 대입
        for (int r = 0; r < splitSize; r++) {
            for (int c = 0; c < splitSize; c++) {
                temp[c][splitSize - 1 - r] = map[sr + r][sc + c];
            }
        }

        for (int r = 0; r < splitSize; r++) {
            for (int c = 0; c < splitSize; c++) {
                map[sr + r][sc + c] = temp[r][c];
            }
        }
    }
}

 

728x90