코딩테스트 겸 종목별로 간단하게 문제를 풀어보기 위해 선정한 시뮬레이션 문제이다.
단순 구현 문제로, 코딩 근육을 좀 풀어볼겸 시도 해 보았다.
CCTV의 위치는 이미 정해져있기 때문에,
방향만 계속해서 변경 해 주면서 사각지대가 최소가 되는 CCTV의 각일 때의 사각지대의 넓이를 구하는 문제다.
따라서 이 'CCTV의 방향을 변경'해 주는 방법을 어떻게 하느냐가 이 문제의 핵심일 것이다.
CCTV의 위치를 매번 변경할 때마다 사각지대를 체크하는 방법을 어떻게 구현해볼까 하는 짧은 고민도 있었지만,
간단한 문제이니만큼 간단하게 구현하기로 했다.
먼저 입력을 받으면서, CCTV가 입력에 들어왔다면, 이 정보들을 별도로 저장한다.
struct CCTV {
int Y, X;
int dir;
int type;
};
int N, M;
cin >> N >> M;
vector<vector<int>> office(N, vector<int>(M));
vector<CCTV> cctvs;
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) {
cctvs.push_back({ i, j, 0, office[i][j] });
}
}
}
마침 이전에 보았던 STL 문법이 next_permutation이였어서,
유사한 방법으로 다음 CCTV의 방향을 정하는 코드를 작성하기로 했다.
next_permutation의 내부 구현은 전혀 모르고 그냥 착안만 했다.
진수 변환 문제처럼, 먼저 해당 카메라 타입의 경우의 수를 카메라의 순서를 고려하여 모두 더한 seed값을 구한 후,
1을 더한 다음 seed값을 다시 적절히 변환하여 다음 순열을 구할 수 있었다.
bool nextperm(vector<CCTV>& cctvs) {
int seed = 0;
int dim = 1;
for (int i = cctvs.size() - 1; i >= 0; i--) {
auto& cctv = cctvs[i];
switch (cctv.type) {
case 2:
seed += dim * cctv.dir;
dim *= 2;
break;
case 5:
break;
default:
seed += dim * cctv.dir;
dim *= 4;
break;
}
}
seed += 1;
if (seed == dim) return false;
for (auto& cctv : cctvs) {
switch (cctv.type) {
case 2:
dim /= 2;
cctv.dir = seed / dim;
seed %= dim;
break;
case 5:
break;
default:
dim /= 4;
cctv.dir = seed / dim;
seed %= dim;
break;
}
}
return true;
}
만약 이미 모든 순회를 완료했다면, false를 반환하여
메인 루프의 조건문에서 순환을 완료했음을 확인하여 종료할 수 있게 작성했다.
메인 루프에서는 주어진 cctv의 방향에 맞추어 사무실 내의 가시영역을 체크해 준 후,
볼 수 없는 곳의 수를 체크하는 로직을 작성했다.
int min = -1;
do {
vector<vector<bool>> insight(N, vector<bool>(M, false));
int blindspot = 0;
for (auto& cctv : cctvs) {
switch (cctv.type) {
case 1:
sightcheck(cctv.dir, insight, cctv.Y, cctv.X, office, N, M);
break;
case 2:
sightcheck(cctv.dir, insight, cctv.Y, cctv.X, office, N, M);
sightcheck((cctv.dir + 2) % 4, insight, cctv.Y, cctv.X, office, N, M);
break;
case 3:
sightcheck(cctv.dir, insight, cctv.Y, cctv.X, office, N, M);
sightcheck((cctv.dir + 1) % 4, insight, cctv.Y, cctv.X, office, N, M);
break;
case 4:
sightcheck(cctv.dir, insight, cctv.Y, cctv.X, office, N, M);
sightcheck((cctv.dir + 2) % 4, insight, cctv.Y, cctv.X, office, N, M);
sightcheck((cctv.dir + 3) % 4, insight, cctv.Y, cctv.X, office, N, M);
break;
case 5:
sightcheck(cctv.dir, insight, cctv.Y, cctv.X, office, N, M);
sightcheck((cctv.dir + 1) % 4, insight, cctv.Y, cctv.X, office, N, M);
sightcheck((cctv.dir + 2) % 4, insight, cctv.Y, cctv.X, office, N, M);
sightcheck((cctv.dir + 3) % 4, insight, cctv.Y, cctv.X, office, N, M);
break;
}
}
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (office[i][j] != 0) continue;
if (insight[i][j] == false) blindspot++;
}
}
if (min == -1 || min > blindspot)
min = blindspot;
} while (nextperm(cctvs));
sightcheck 함수는 해당 cctv의 위치를 기준으로 타입별로 적절하게 탐지영역을 체크해주는 함수로,
반복적으로 사용되는 구간을 줄이기 위해 재사용했다.
bool oob(const int& Y, const int& X, const int& N, const int& M) {
if (Y < 0 || X < 0 || Y >= N || X >= M)
return true;
else
return false;
}
void sightcheck(const int& dir, vector<vector<bool>>& insight, const int& start_y, const int& start_x, vector<vector<int>>& office, const int& N, const int& M) {
int y = start_y, x = start_x;
while (true) {
y += fways[dir][0];
x += fways[dir][1];
if (oob(y, x, N, M)) break;
if (office[y][x] == 6) break;
insight[y][x] = true;
}
}
다음 카메라의 방향의 조합을 구하는 next_perm 함수에서 오류가 몇 번 있었지만, 그 외에는 한번에 통과했다.
이런 구현문제부터 더 복잡한 문제들까지, 논리적으로 코드를 작성하는 버릇을 들여야
에러를 고치는 시간을 줄일 수 있을것이라고 생각한다.
코딩 테스트에서도 디버깅을 금지하는 경우도 있고,
실무에서는 멀티스레딩이거나 또 별도의 이유로 디버깅이 어려운 경우가 있기 때문에,
코드를 작성했을 때 에러를 최소한으로 발생할 수 있도록 신중하게 작성해야 한다고 생각한다.