[C++] BOJ 15683번 - 감시
학교에서 진행하는 수업에서 어렵다고 생각하는 알고리즘 문제를 공유하고, 문제를 풀어보았다.
문제의 난이도는 골드 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;
}