ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [C++] BOJ 15683번 - 감시
    프로그래밍/알고리즘 PS 2024. 5. 23. 21:05

    학교에서 진행하는 수업에서 어렵다고 생각하는 알고리즘 문제를 공유하고, 문제를 풀어보았다.

     

    문제의 난이도는 골드 3으로, 정답률은 약 45%이다. 시간 제한은 1초이다.

    문제 링크

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

    문제 설명

     사무실에 총 5가지 종류의 CCTV가 설치되어 있다. 각 CCTV는 감시할 수 있는 영역이 제한되어 있는데 벽을 뚫을 수 없고, 90도 회전이 가능하다. 이 때 사무실의 크기와 상태, CCTV의 위치가 주어졌을 때 사각 지대의 최소 크기를 구하는 것이 이번 문제이다.

    문제 분석

     사무실의 상태가 입력으로 들어온다. 먼저 세로 크기 N과 가로 크기 M을 입력받으며 0은 빈칸, 1 ~ 5는 CCTV, 6은 벽을 나타내는 각 칸의 정보가 주어진다.

     해당 입력에 대해 사각 지대의 최소 크기를 구하는 것이 이번 문제이다.

     

     사각 지대의 최소 크기를 구하기 위해서는 모든 cctv가 감시한 경우의 수에 대한 사각 지대의 크기를 확인하고, 그 중 최소 크기의 사각 지대를 구해야 한다. 즉, 브루트 포스로 사각 지대가 나타나는 모든 경우의 수를 확인해야 한다는 뜻이다.

     

     그리고 직관적으로 각 경우의 수를 체크하기에 백트래킹이 적합하다는 느낌이 들어 백트래킹으로 문제를 해결하였다. 첫 cctv부터 마지막 cctv까지 하나 하나의 cctv에 대한 감시 영역에 대한 경우의 수를 백트래킹을 통해 만들어간다.

    문제 풀이

     문제에서 값을 입력받을 때에 먼저 cctv들을 따로 모을 수 있도록 입력 코드를 구성하였다.

    cin >> N >> M;
    
    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < M; j++)
        {
            cin >> originalMap[i][j];
            if (1 <= originalMap[i][j] && originalMap[i][j] <= 5)
                cctvPoints.push_back({i, j});
        }
    }

     

     만일 입력받은 맵의 상태가 1 ~ 5 (cctv)일 경우, cctvPoints라는 vector 타입에 좌표값을 추가해준다.

     

     이 때 맵의 가로와 세로의 길이는 1 ~ 8이라고 주어졌으므로 originalMap 변수를 포함해 필요한 변수는 아래와 같이 전역 변수로 선언하였다. 답으로 출력할 answer 변수는 일단 큰 값인 987654321을 저장하고, 나중에 최소 사각지대 크기의 값으로 변경하도록 구현하였다.

    int originalMap[10][10];
    vector<pair<int, int>> cctvPoints;
    
    int answer = 987654321;
    int N, M;

     

     특정 방향을 따라 cctv가 관찰할 수 있는 영역에 '#'을 표기하는 함수도 구현하였다. 함수명은 check이며, cctv의 좌표와 방향(0, 1, 2, 3)을 주면 단방향으로 '#'을 그려준다. 당연하지만 '#'은 벽을 뚫을 수 없다. direction %= 4 코드를 삽입한 이유는 3보다 큰 값이 들어오더라도 정상적으로 코드가 처리되도록 하기 위함이다.

    void check(pair<int, int> &cctvPoint, int direction)
    {
        direction %= 4;
    
        int y = cctvPoint.first;
        int x = cctvPoint.second;
    
        while (true)
        {
            y += dy[direction];
            x += dx[direction];
    
            if (x < 0 || x >= M || y < 0 || y >= N)
                break;
            if (originalMap[y][x] == 6)
                break;
            if (originalMap[y][x] == 0)
                originalMap[y][x] = '#';
        }
    }
    int dy[4] = {0, -1, 0, 1};
    int dx[4] = {1, 0, -1, 0};

     

     위와 같이 direction을 활용한 코드를 사용하기 위해서는 dy, dx 변수를 선언해주어야 한다. 이 때, CCTV의 회전을 적절하게 표현하기 위해서는 dy와 dx의 값의 순서를 적절하게 구성해야 한다. 지금은 좌, 하, 우, 상 순으로 구성되어 있다.

    void backtracking(int depth)
    {
        pair<int, int> cctvPoint = cctvPoints[depth];
        int y = cctvPoint.first;
        int x = cctvPoint.second;
    
        int tempMap[10][10];
    
        for (int i = 0; i < N; i++)
            for (int j = 0; j < M; j++)
                tempMap[i][j] = originalMap[i][j];
    
        for (int i = 0; i < 4; i++)
        {
            if (originalMap[y][x] == 1)
            {
                check(cctvPoint, i);
            }
            else if (originalMap[y][x] == 2)
            {
                check(cctvPoint, i);
                check(cctvPoint, i + 2);
            }
            else if (originalMap[y][x] == 3)
            {
                check(cctvPoint, i);
                check(cctvPoint, i + 1);
            }
            else if (originalMap[y][x] == 4)
            {
                check(cctvPoint, i);
                check(cctvPoint, i + 1);
                check(cctvPoint, i + 3);
            }
            else if (originalMap[y][x] == 5)
            {
                check(cctvPoint, i);
                check(cctvPoint, i + 1);
                check(cctvPoint, i + 2);
                check(cctvPoint, i + 3);
            }
    
            backtracking(depth + 1);
    
            for (int p = 0; p < N; p++)
                for (int q = 0; q < M; q++)
                    originalMap[p][q] = tempMap[p][q];
        }
    }

     

     백트래킹 함수에서는 cctv의 좌표를 읽어와, check 함수를 활용하여 각 가능한 경우의 감시 구조를 만들어낸다. 이 때 for문을 돌리는 이유는 CCTV의 direction 90도 회전을 표현하기 위함이다.

     tempMap 변수를 활용하는 이유는, 백트래킹 함수가 return되어 돌아왔을 때 originalMap이 이전 상태로 돌아오도록 하기 위함이다.

     

     위 코드에는 depth가 적절한 크기가 되었을 때에 대한 처리가 빠져있다. 해당 부분도 코드로 작성하면 다음과 같다.

    if (depth == cctvPoints.size())
    {
        int numberOfZero = 0;
        for (int i = 0; i < N; i++)
            for (int j = 0; j < M; j++)
                if (originalMap[i][j] == 0)
                    numberOfZero++;
        answer = min(answer, numberOfZero);
        return;
    }

    해당 코드는 backtracking 함수 최상단에 위치하는 코드이다. 모든 cctv에 대하여 영역 처리가 끝난 상황을 의미하며, 이 때에는 0의 갯수를 세보고 최소 0의 갯수를 구한 뒤 함수에서 return한다.

     

    전체 코드는 아래와 같다.

    전체 코드

    #include <iostream>
    #include <vector>
    #include <utility>
    #include <algorithm>
    
    using namespace std;
    
    int originalMap[10][10];
    vector<pair<int, int>> cctvPoints;
    
    int answer = 987654321;
    int N, M;
    
    int dy[4] = {0, -1, 0, 1};
    int dx[4] = {1, 0, -1, 0};
    
    void check(pair<int, int> &cctvPoint, int direction)
    {
        direction %= 4;
    
        int y = cctvPoint.first;
        int x = cctvPoint.second;
    
        while (true)
        {
            y += dy[direction];
            x += dx[direction];
    
            if (x < 0 || x >= M || y < 0 || y >= N)
                break;
            if (originalMap[y][x] == 6)
                break;
            if (originalMap[y][x] == 0)
                originalMap[y][x] = '#';
        }
    }
    
    void backtracking(int depth)
    {
        if (depth == cctvPoints.size())
        {
            int numberOfZero = 0;
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    if (originalMap[i][j] == 0)
                        numberOfZero++;
            answer = min(answer, numberOfZero);
            return;
        }
    
        pair<int, int> cctvPoint = cctvPoints[depth];
        int y = cctvPoint.first;
        int x = cctvPoint.second;
    
        int tempMap[10][10];
    
        for (int i = 0; i < N; i++)
            for (int j = 0; j < M; j++)
                tempMap[i][j] = originalMap[i][j];
    
        for (int i = 0; i < 4; i++)
        {
            if (originalMap[y][x] == 1)
            {
                check(cctvPoint, i);
            }
            else if (originalMap[y][x] == 2)
            {
                check(cctvPoint, i);
                check(cctvPoint, i + 2);
            }
            else if (originalMap[y][x] == 3)
            {
                check(cctvPoint, i);
                check(cctvPoint, i + 1);
            }
            else if (originalMap[y][x] == 4)
            {
                check(cctvPoint, i);
                check(cctvPoint, i + 1);
                check(cctvPoint, i + 3);
            }
            else if (originalMap[y][x] == 5)
            {
                check(cctvPoint, i);
                check(cctvPoint, i + 1);
                check(cctvPoint, i + 2);
                check(cctvPoint, i + 3);
            }
    
            backtracking(depth + 1);
    
            for (int p = 0; p < N; p++)
                for (int q = 0; q < M; q++)
                    originalMap[p][q] = tempMap[p][q];
        }
    }
    
    int main(void)
    {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        cin >> N >> M;
    
        for (int i = 0; i < N; i++)
        {
            for (int j = 0; j < M; j++)
            {
                cin >> originalMap[i][j];
                if (1 <= originalMap[i][j] && originalMap[i][j] <= 5)
                    cctvPoints.push_back({i, j});
            }
        }
    
        backtracking(0);
    
        cout << answer << '\n';
    
        return 0;
    }

     

    여담

    처음에는 direction을 사용하지 않고 작성한 코드로 문제를 풀고 맞추었는데, 코드가 너무 길고 장황하여 인터넷을 참고하여 정리한 코드가 지금의 코드이다.

     

    #include <iostream>
    #include <vector>
    #include <utility>
    
    using namespace std;
    
    int originalMap[10][10];
    vector<pair<int, int>> cctvPoints;
    
    int answer = 987654321;
    int N, M;
    
    void moveRight(pair<int, int> &point, int valueFrom, int valueTo)
    {
        int y = point.first;
        int x = point.second;
        int nx = x + 1;
        while (nx < M)
        {
            if (originalMap[y][nx] == 6)
                break;
            if (originalMap[y][nx] == valueFrom)
                originalMap[y][nx] = valueTo;
            nx++;
        }
    }
    
    void moveLeft(pair<int, int> &point, int valueFrom, int valueTo)
    {
        int y = point.first;
        int x = point.second;
        int nx = x - 1;
        while (nx >= 0)
        {
            if (originalMap[y][nx] == 6)
                break;
            if (originalMap[y][nx] == valueFrom)
                originalMap[y][nx] = valueTo;
            nx--;
        }
    }
    
    void moveUp(pair<int, int> &point, int valueFrom, int valueTo)
    {
        int y = point.first;
        int x = point.second;
        int ny = y - 1;
        while (ny >= 0)
        {
            if (originalMap[ny][x] == 6)
                break;
            if (originalMap[ny][x] == valueFrom)
                originalMap[ny][x] = valueTo;
            ny--;
        }
    }
    
    void moveDown(pair<int, int> &point, int valueFrom, int valueTo)
    {
        int y = point.first;
        int x = point.second;
        int ny = y + 1;
        while (ny < N)
        {
            if (originalMap[ny][x] == 6)
                break;
            if (originalMap[ny][x] == valueFrom)
                originalMap[ny][x] = valueTo;
            ny++;
        }
    }
    
    void backtracking(int depth)
    {
        if (depth == cctvPoints.size())
        {
            int numberOfZero = 0;
            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < M; j++)
                {
                    if (originalMap[i][j] == 0)
                        numberOfZero++;
                }
            }
            answer = min(answer, numberOfZero);
            return;
        }
    
        pair<int, int> cctvPoint = cctvPoints[depth];
        int y = cctvPoint.first;
        int x = cctvPoint.second;
    
        int tempMap[10][10];
    
        for (int i = 0; i < N; i++)
            for (int j = 0; j < M; j++)
                tempMap[i][j] = originalMap[i][j];
    
        if (originalMap[y][x] == 1)
        {
            moveRight(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveLeft(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveUp(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveDown(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
        }
        else if (originalMap[y][x] == 2)
        {
            moveLeft(cctvPoint, 0, '#');
            moveRight(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveUp(cctvPoint, 0, '#');
            moveDown(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
        }
        else if (originalMap[y][x] == 3)
        {
            moveLeft(cctvPoint, 0, '#');
            moveUp(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveLeft(cctvPoint, 0, '#');
            moveDown(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveRight(cctvPoint, 0, '#');
            moveUp(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveRight(cctvPoint, 0, '#');
            moveDown(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[j][i] = tempMap[j][i];
        }
        else if (originalMap[y][x] == 4)
        {
            moveUp(cctvPoint, 0, '#');
            moveLeft(cctvPoint, 0, '#');
            moveRight(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveDown(cctvPoint, 0, '#');
            moveLeft(cctvPoint, 0, '#');
            moveRight(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveUp(cctvPoint, 0, '#');
            moveLeft(cctvPoint, 0, '#');
            moveDown(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
    
            moveUp(cctvPoint, 0, '#');
            moveDown(cctvPoint, 0, '#');
            moveRight(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
        }
        else if (originalMap[y][x] == 5)
        {
            moveUp(cctvPoint, 0, '#');
            moveDown(cctvPoint, 0, '#');
            moveLeft(cctvPoint, 0, '#');
            moveRight(cctvPoint, 0, '#');
            backtracking(depth + 1);
            for (int i = 0; i < N; i++)
                for (int j = 0; j < M; j++)
                    originalMap[i][j] = tempMap[i][j];
        }
    }
    
    int main(void)
    {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        cin >> N >> M;
    
        for (int i = 0; i < N; i++)
        {
            for (int j = 0; j < M; j++)
            {
                cin >> originalMap[i][j];
                if (1 <= originalMap[i][j] && originalMap[i][j] <= 5)
                    cctvPoints.push_back({i, j});
            }
        }
    
        backtracking(0);
    
        cout << answer << '\n';
    
        return 0;
    }
Designed by Tistory.