[ 백준 ] 15683 / 감시

金弘均·2021년 9월 16일
0

Baekjoon Online Judge

목록 보기
143/228
post-thumbnail
post-custom-banner

# Appreciation

/*
 * Problem :: 15683 / 감시
 *
 * Kind :: Simulation
 *
 * Insight
 * - 음... 한번에 사각지대를 최소화 하는 CCTV 의 방향들을 알 수 있는
 *   알고리즘은 발견하기 힘들것 같다...
 *   그냥 다 해볼 수 있나?
 *   + max(N)=8, max(M)=8
 *     CCTV 가 64개 있으면 4*64 인데
 *     최대 CCTV 의 개수가 8개 이므로 4*8 이니
 *     시간 제한 내에 풀 수 있을 것 같다
 *
 * - CCTV 의 방향을 어떻게 설정할 수 있을까?
 *   L = 왼쪽, R = 오른쪽, D = 아래쪽, U = 위쪽 이라고 하면...
 *   + 1번 CCTV : 4가지 종류의 방향 ([L], [R], [D], [U])
 *     2번 CCTV : 2가지 종류의 방향 ([L,R], [D,U])
 *     3번 CCTV : 4가지 종류의 방향 ([U,R], [R,D], [D,L], [L,U])
 *     4번 CCTV : 4가지 종류의 방향 ([L,U,R], [U,R,D], [R,D,L], [D,L,U])
 *     5번 CCTV : 1가지 종류의 방향 ([L,U,R,D])
 *     (톱니 배열... Vector 를 활용해서 넣어주자)
 *     # DFS 도는 것처럼 재귀로 그때 그때 CCTV 의 방향을 설정해 준 뒤,
 *       모든 CCTV 의 방향을 설정해 주었으면 그때 사각지대의 개수를 구하자
 *
 * - 공통적인 작업이 필요하다
 *   + CCTV 의 위치와 방향이 주어졌을 때, 사무실의 각 칸의 감시상태를 갱신해야한다
 *     # CCTV 를 주어진 방향대로 설치했을 때, 각 칸의 감시상태 갱신하는 함수를 만들자
 *     # CCTV 를 제거했을 대, 각 칸의 감시상태를 갱신하는 함수를 만들자
 *
 * Point
 * - 0 0 1
 *   0 1 0
 *   이라고 초기 사무실의 칸이 주어졌을 때,
 *   + (0,2) 에 위치한 1번 CCTV 가 아래쪽을 감시하고
 *     (1,1) 에 위치한 1번 CCTV 가 오른쪽을 감시하면
 *     0 0 1
 *     0 1 #
 *     이 된다
 *     # 하지만 (1,1) CCTV 를 제거할 때,
 *       0 0 1
 *       0 1 0
 *       으로 상태를 갱신하면 안된다. 왜냐면 (0,2) CCTV 는 그 칸을 계속 감시중이기 때문이다
 *       -> 즉, 같은 칸이 여러 CCTV 에 중복으로 감시당하는 것을 추적해야한다.
 *          코드에서는 이러한 칸의 경우 -1 을 해줌으로써 해결하였다
 *          (이는 어떤 칸이 -3 인 경우는 3개의 CCTV 가 그 칸을 감시하는 것을 뜻한다)
 *
 * - CCTV 가 있는 칸은 감시당하는 상태로 간주됨을 유의하자
 */

# Code

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

#include <iostream>
#include <vector>

using namespace std;

#define endl '\n'

// Set up : Global Variables
int N, M;
int office[8][8];
struct CCTV { int y, x, no; }; /* CCTV 의 y 좌표, x 좌표, 번호 */
vector<CCTV> cctvs; /* 사무실의 CCTV 의 정보가 담겨진 Vector */
int dy[4] = {+1, 0, 0, -1};
int dx[4] = {0, +1, -1, 0};
enum Direction { D, R, L, U }; /* 가독성을 높이기 위해 방향을 나타내는 열거형 정의 */
vector<vector<int>> dir[5+1]; /* dir[cctv.no] = cctv.no 에 맞는 방향 정보가 담겨져 있음 */
#define INF 987654321

// Set up : Functions Declaration
int f(int idx);
bool isValid(int y, int x);
void setUp(int y, int x, int d);
void allSet(int y, int x, const vector<int>& dirs);
void rollBack(int y, int x, int d);
void allBack(int y, int x, const vector<int>& dirs);


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

    // Process
    /* dir[cctv.no] = cctv.no 에 맞는 방향 정보를 넣어줌 */
    dir[1] = { {R}, {D}, {L}, {U} };
    dir[2] = { {L,R}, {D,U} };
    dir[3] = { {U,R}, {R,D}, {D,L}, {L,U} };
    dir[4] = { {L,U,R}, {U,R,D}, {R,D,L}, {D,L,U} };
    dir[5] = { {L,U,R,D} };

    // Set up : Input
    cin >> N >> M;
    for (int i=0; i<N; i++) {
        for (int j=0; j<M; j++) {
            cin >> office[i][j];
            if (office[i][j] > 0 && office[i][j] < 6) {
                /* CCTV 의 위치 및 번호 정보 수집 */
                cctvs.push_back({i, j, office[i][j]});
            }
        }
    }

    // Process
    int ans = f(0);

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

// Helper Functions
int f(int idx)
/* CCTV 들의 가능한 모든 방향 조합을 탐색한 후,
 * 최소 사각지대의 개수를 반환 */
{
    /* 모든 CCTV 의 방향을 설정 하고 각 칸의 감시상태가 그에 맞게 갱신되었으면 */
    if (idx == cctvs.size()) {
        /* 사각지대의 크기를 구하고 그 값을 반환 */
        int blind = 0;
        for (int i=0; i<N; i++) {
            for (int j=0; j<M; j++) {
                /* 감시당하지 않는 칸은 그 값이 0임 */
                if (office[i][j] == 0) {
                    blind++;
                }
            }
        }
        return blind;
    }

    int ret = INF; /* 최소 사각지대의 크기 */

    CCTV cctv = cctvs[idx]; /* 현재 CCTV 정보 얻음 */
    /* dir[3] = { {U,R}, {R,D}, {D,L}, {L,U} }; 의 경우
     * {U,R}, {R,D}, {D,L}, {L,U} 가 순서대로 dirs 가 되어 나온다 */
    for (const vector<int>& dirs : dir[cctv.no]) {
        allSet(cctv.y, cctv.x, dirs); /* 주어진 방향 정보(dirs) 에 따라
                                       * 감시카메라 설치 및 칸의 감시상태 갱신 */
        ret = min(ret, f(idx+1)); /* 이때, 가능한 최소 사각지대의 크기를 갱신 */
        allBack(cctv.y, cctv.x, dirs); /* 주어진 방향 정보(dirs) 에 따라
                                        * 감시카메라 제거 및 칸의 감시상태 갱신 */
    }

    return ret;
}

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

void setUp(int y, int x, int d)
/* CCTV 의 위치와 방향이 주어졌을 때,
 * CCTV 를 주어진 방향대로 설치하여 그에 따른 각 칸의 감시상태를 갱신 */
{
    int ny = y + dy[d];
    int nx = x + dx[d];
    /* 주어진 방향으로 한 칸 움직인 다음 좌표가 유효하고 벽이 아니라면 그 좌표로 이동 */
    while (isValid(ny, nx) && office[ny][nx] != 6) {
        /* 현재 좌표가 CCTV 가 설치된 칸이 아니라면 */
        if (office[ny][nx] <= 0) {
            /* 감시당하고 있음 (현재 CCTV 에) */
            office[ny][nx]--;
        }
        /* 다음 좌표 갱신 */
        ny += dy[d];
        nx += dx[d];
    }
}

void allSet(int y, int x, const vector<int>& dirs)
/* CCTV 의 위치와 방향들이 주어졌을 때,
 * CCTV 를 주어진 방향들대로 설치하여 그에 따른 각 칸의 감시상태를 갱신 */
{
    for (int d : dirs) {
        setUp(y, x, d);
    }
}

void rollBack(int y, int x, int d)
/* CCTV 의 위치와 방향이 주어졌을 때,
 * CCTV 를 주어진 방향에서 제거하여 그에 따른 각 칸의 감시상태를 갱신 */
{
    int ny = y + dy[d];
    int nx = x + dx[d];
    /* 주어진 방향으로 한 칸 움직인 다음 좌표가 유효하고 벽이 아니라면 그 좌표로 이동 */
    while (isValid(ny, nx) && office[ny][nx] != 6) {
        /* 현재 좌표가 감시당하는 칸이라면 */
        if (office[ny][nx] < 0) {
            /* 더이상 감시당하지 않음 (현재 CCTV 에는) */
            office[ny][nx]++;
        }
        /* 다음 좌표 갱신 */
        ny += dy[d];
        nx += dx[d];
    }
}

void allBack(int y, int x, const vector<int>& dirs)
/* CCTV 의 위치와 방향들이 주어졌을 때,
 * CCTV 를 주어진 방향들에서 제거하여 그에 따른 각 칸의 감시상태를 갱신 */
{
    for (int d : dirs) {
        rollBack(y, x, d);
    }
}
profile
이런 미친 게임을 봤나! - 옥냥이
post-custom-banner

0개의 댓글