[ 백준 ] 2638 / 치즈

金弘均·2021년 9월 14일
0

Baekjoon Online Judge

목록 보기
23/228
post-thumbnail

# Appreciation

/*
 * Problem :: 2638 / 치즈
 *
 * Kind :: Simulation + BFS
 *
 * Insight
 * - 모눈종이에서 시간 마다 외부 공기와 치즈,
 *   그리고 치즈 내부의 공간을 구별해야 한다
 *   + 모눈종이의 맨 가장자리에는 치즈가 놓이지 않는다
 *     즉, 모눈종이의 맨 가장자리는 항상 외부공기이다
 *     # 이와 같은 사실을 통해 시간이 흐를때마다
 *       가장자리부터의 BFS 를 통해 외부 공기인 격자를 알아내고
 *       이를 바탕으로 녹을 치즈를 찾아주자
 *
 * Point
 * - 치즈 격자는 3가지 상태가 될 수 있다
 *   외부 공기, 치즈, 치즈 내부의 공간
 *   이를 enum 을 사용하여 AIR, CHEESE, HOLE 으로 다루어주었다
 *
 * - CHEESE 는 녹아서 AIR 가 되고
 *   HOLE 은 CHEESE 가 녹음에 따라 AIR 와 맞닿게 되면 자신도 따라서 AIR 가 된다
 *   + 이는 CHEESE 와 HOLE 은 AIR 인 치즈 격자를 갱신할 때 같이 다루어 줄 수 있다는 뜻이다
 *     처음에 주어진 모눈종이에서 HOLE 과 CHEESE 인 치즈 격자칸들을 찾아주고
 *     이후에는 새롭게 AIR 가 된 칸들을 갱신하는 것이
 *     동시에 HOLE 과 CHEESE 에서 AIR 가 된 칸들을 갱신하는 것으로 생각할 수 있다
 *     # CHEESE 칸들은 입력에서 주어지니
 *       HOLE 칸들만 처음 모눈종이가 주어졌을 때 이 상태에 해당하는 치즈 격자칸들을 찾아주면 된다
 *       -> 문제에서 요구하는 최적화는 위와 같은데,
 *          만약 AIR 칸들을 갱신한 후 추가로 HOLE 이나 CHEESE 칸들을 갱신하는 로직이 있다면
 *          시간초과가 나는 것 같다
 *          => 그래서 필자는 0=HOLE, 1=CHEESE, 2=AIR 로 하여
 *             입력의 0 이 일단 모두 HOLE 칸들로 가정 후
 *             가장 자리 BFS 를 통해 AIR 칸들을 갱신하였다
 *             이렇게 하면 HOLE 이나 CHEESE 칸들 갱신에 추가적인 로직을 사용하지 않아도 된다
 */

# Code

//
//  BOJ
//  ver.C++
//
//  Created by GGlifer
//
//  Open Source

#include <iostream>
#include <queue>
#include <cstring>
#include <vector>

using namespace std;

#define endl '\n'

// Set up : Global Variables
int N, M;
int A[100][100];
struct Point { int y, x; };
enum { HOLE, CHEESE, AIR };
int dy[4] = {+1, 0, 0, -1};
int dx[4] = {0, +1, -1, 0};

// Set up : Functions Declaration
bool isValid(int y, int x);
void marking();
bool melting();


int main()
{
    // Set up : I/O
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // Set up : Input
    cin >> N >> M;
    int res = 0;
    for (int i=0; i<N; i++) {
        for (int j=0; j<M; j++) {
            cin >> A[i][j];
            if (A[i][j] > 0) res++;
        }
    }

    // Process
    int time = 0; /* 치즈가 모두 녹아 없어지는데 걸리는 시간 */
    while (melting()) { /* 치즈가 녹았다면 */
        time++;
    }

    // Control : Output
    cout << time << endl;
}

// Helper Functions
bool isValid(int y, int x)
/* 주어진 좌표 (y, x) 가 유효한 좌표면 true 를 반환, 그 외 false 를 반환 */
{
    return y >= 0 && y < N && x >= 0 && x < M;
}

void marking()
/* 치즈 격자칸들의 상태 갱신
 * = 가장자리 BFS 를 통해 AIR 칸들을 갱신
 * = 동시에 CHEESE, HOLE 칸들을 갱신 */
{
    bool isVisited[N][M];
    memset(isVisited, false, sizeof(isVisited));
    queue<Point> que;

    A[0][0] = AIR; /* 가장자리는 항상 외부 공기 */
    isVisited[0][0] = true;
    que.push({0, 0});

    while (not(que.empty())) {
        int cy = que.front().y;
        int cx = que.front().x;
        que.pop();

        for (int i=0; i<4; i++) {
            int ny = cy + dy[i];
            int nx = cx + dx[i];
            if (isValid(ny, nx) && not(isVisited[ny][nx]) && A[ny][nx] != CHEESE) {
                isVisited[ny][nx] = true;
                A[ny][nx] = AIR;
                que.push({ny, nx});
            }
        }
    }
}

bool melting()
/* 치즈가 녹는 것을 구현한 함수 */
{
    marking(); /* 한 시간 후 치즈 격자칸들의 상태 갱신 */
    vector<Point> P; /* 녹을 치즈 들의 좌표들 */
    for (int i=0; i<N; i++) {
        for (int j=0; j<M; j++) {
            if (A[i][j] == CHEESE) { /* 현재 칸에 치즈가 들어있다면 */
                int cnt = 0; /* 외부 공기와 접촉횟수 */
                for (int k=0; k<4; k++) {
                    int ay = i + dy[k];
                    int ax = j + dx[k];
                    if (isValid(ay, ax) && A[ay][ax] == AIR) cnt++;
                }

                if (cnt >= 2) { /* 외부 공기와 2번 이상 접촉했다면 */
                    P.push_back({i, j}); /* 현재 칸의 치즈는 녹을 예정 */
                }
            }
        }
    }

    /* 치즈가 녹아 외부 공기가 들어오는 칸이 됨 */
    for (auto [py, px] : P) {
        A[py][px] = AIR;
    }

    /* 치즈가 녹았다면 true 를 반환, 그렇지 않으면 false 를 반환 */
    return not(P.empty());
}
profile
이런 미친 게임을 봤나! - 옥냥이

0개의 댓글