백준 15683 감시

김헌규·2025년 6월 9일
post-thumbnail

오늘은 감시라는 문제를 풀어보았다. 백트래킹 문제이며 현재 기준으로 골드 3 문제이다. 골드 4에서 골드 3으로 오자마자 구현에서 많은 것을 요구하여 힘들었던 문제였다.



🔥 문제

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

스타트링크의 사무실은 1×1크기의 정사각형으로 나누어져 있는 N×M 크기의 직사각형으로 나타낼 수 있다. 사무실에는 총 K개의 CCTV가 설치되어져 있는데, CCTV는 5가지 종류가 있다. 각 CCTV가 감시할 수 있는 방법은 다음과 같다.

1번 CCTV는 한 쪽 방향만 감시할 수 있다. 2번과 3번은 두 방향을 감시할 수 있는데, 2번은 감시하는 방향이 서로 반대방향이어야 하고, 3번은 직각 방향이어야 한다. 4번은 세 방향, 5번은 네 방향을 감시할 수 있다.

CCTV는 감시할 수 있는 방향에 있는 칸 전체를 감시할 수 있다. 사무실에는 벽이 있는데, CCTV는 벽을 통과할 수 없다. CCTV가 감시할 수 없는 영역은 사각지대라고 한다.

CCTV는 회전시킬 수 있는데, 회전은 항상 90도 방향으로 해야 하며, 감시하려고 하는 방향이 가로 또는 세로 방향이어야 한다.

지도에서 0은 빈 칸, 6은 벽, 1~5는 CCTV의 번호이다. 위의 예시에서 1번의 방향에 따라 감시할 수 있는 영역을 '#'로 나타내면 아래와 같다.

CCTV는 벽을 통과할 수 없기 때문에, 1번이 → 방향을 감시하고 있을 때는 6의 오른쪽에 있는 칸을 감시할 수 없다.

위의 예시에서 감시할 수 있는 방향을 알아보면 아래와 같다.

CCTV는 CCTV를 통과할 수 있다. 아래 예시를 보자.

위와 같은 경우에 2의 방향이 ↕ 3의 방향이 ←와 ↓인 경우 감시받는 영역은 다음과 같다.

사무실의 크기와 상태, 그리고 CCTV의 정보가 주어졌을 때, CCTV의 방향을 적절히 정해서, 사각 지대의 최소 크기를 구하는 프로그램을 작성하시오.


입력

첫째 줄에 사무실의 세로 크기 N과 가로 크기 M이 주어진다. (1 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에는 사무실 각 칸의 정보가 주어진다. 0은 빈 칸, 6은 벽, 1~5는 CCTV를 나타내고, 문제에서 설명한 CCTV의 종류이다.

CCTV의 최대 개수는 8개를 넘지 않는다.


출력

첫째 줄에 사각 지대의 최소 크기를 출력한다.




✍️ 문제 풀이

처음에 cctv가 방향별로 감시했는 부분을 표시한 후 다음 조합을 구할 때 기존의 값을 지우는 방법으로 구현하려고 했으나 그렇게 하면 너무 복잡해져서 다른 방법을 고민했지만 도저히 생각이 안 나서 다른 분의 코드를 참조하였다.


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;


public class Main {
    public static class Cctv {
        int x;
        int y;
        int type;

        public Cctv(int x, int y, int type) {
            this.x = x;
            this.y = y;
            this.type = type;
        }
    }

    static int N;
    static int M;

    static ArrayList<Cctv> cctvs = new ArrayList<>();
    
    // 상하좌우
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1};

    static int[][][] direction = {{{0}}, {{0}, {1}, {2}, {3}}, {{0, 1}, {2, 3}}, {{0, 3}, {1, 3}, {1, 2}, {0, 2}}, {{0, 2, 3}, {0, 1, 3}, {1, 2, 3}, {0, 1, 2}}, {{0, 1, 2, 3}}};

    static int result = Integer.MAX_VALUE;

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

        StringTokenizer info = new StringTokenizer(br.readLine());

        N = Integer.parseInt(info.nextToken());
        M = Integer.parseInt(info.nextToken());

        int[][] arr = new int[N][M];

        for (int i = 0; i < N; i++) {
            StringTokenizer nums = new StringTokenizer(br.readLine());
            for (int j = 0; j < M; j++) {
                int num = Integer.parseInt(nums.nextToken());
                arr[i][j] = num;
                // cctv 정보 저장
                if (num != 0 && num != 6) {
                    cctvs.add(new Cctv(i, j, num));
                }
            }
        }

        dfs(0, arr);

        System.out.println(result);

    }

    // 조합
    private static void dfs(int depth, int[][] map) {
        // 종료조건
        if (depth == cctvs.size()) {
            int count = countZero(map);
            result = Math.min(count, result);
            return;
        }

        int type = cctvs.get(depth).type;
        int x = cctvs.get(depth).x;
        int y = cctvs.get(depth).y;

        for (int i = 0; i < direction[type].length; i++) {

            int[][] newMap = copyArr(map);

            for (int j = 0; j < direction[type][i].length; j++) {

                int dir = direction[type][i][j];
                int nx = x + dx[dir];
                int ny = y + dy[dir];

                while (true) {

                    if (nx < 0 || nx >= N || ny < 0 || ny >= M) {
                        break;
                    }

                    if (map[nx][ny] == 6) {
                        break;
                    }

                    newMap[nx][ny] = -1;
                    nx += dx[dir];
                    ny += dy[dir];
                }
            }

            dfs(depth + 1, newMap);
        }
    }

    private static int[][] copyArr(int[][] map) {
        int[][] newMap = new int[N][M];

        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                newMap[i][j] = map[i][j];
            }
        }

        return newMap;
    }

    private static int countZero(int[][] map) {
        int count = 0;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < M; j++) {
                if (map[i][j] == 0) {
                    count++;
                }
            }
        }

        return count;
    }
}

위의 코드를 보자면 cctv의 타입별로 방향을 3차원 배열로 저장한 다음 dfs로 조합을 하면서 구현하였다. 표시할 배열은 다음 cctv의 감시 범위를 표시하기 전에 깊은 복사로 배열을 하나 더 만들서 표시하였다. 다음으로 depth가 cctv의 갯수와 같다면 결과를 구하고 재귀를 종료하는 방식이다.

이 문제를 풀면서 구현력을 더 발전시켜야 할 것 같다....

profile
꾸준하게 가자

0개의 댓글