https://www.acmicpc.net/problem/15683
15683번: 감시
스타트링크의 사무실은 1×1크기의 정사각형으로 나누어져 있는 N×M 크기의 직사각형으로 나타낼 수 있다. 사무실에는 총 K개의 CCTV가 설치되어져 있는데, CCTV는 5가지 종류가 있다. 각 CCTV가 감
www.acmicpc.net
문제 이해
역시 모든 경우를 탐색해야 하는 문제이다.
CCTV의 최대 개수가 8개라고 하는 거부터가 그냥 DFS를 써라~ 하는 것 같다.
CCTV는 다섯개의 종류가 있고,
1번, 3번, 4번의 경우 방향을 돌리면 4가지 경우의 수
2번의 경우 2가지 경우의 수
5번의 경우 1가지 경우의 수
가 있다.
i번 CCTV가 j방향을 가리킬 때 동서남북 중 k방향을 감시할 수 있는지를 dir라는 배열에 저장했다.
그리고 입력을 받을 때 CCTV의 위치와 종류를 C라는 배열에 저장해두었다.
DFS()
DFS를 통해서 각 CCTV의 방향을 결정한다.
예를 들어, CCTV가 3, 4, 5번으로 총 3개가 들어왔다면
3번 (4가지) * 4번(4가지) * 5번(1가지) = 16가지의 경우의 수를 따지면 된다.
다시 말해, 3개의 depth를 가지는 DFS를 수행하면 된다.
sel이라는 배열에 3개의 CCTV의 방향을 저장해둔다.
만약 3개의 CCTV가 가리키는 방향을 다 정했다면, 이거에 맞춰서 맵에 표시를 해보면 된다.
표시는 Print_Sel()이라는 함수를 이용했다.
void DFS(int cnt) {
// 종료
if (cnt >= ccnt) {
// 다 골랐다.
int res = Try_Watch();
if (res < ans) ans = res;
return;
}
// idx번을 고를 차례
if (C[cnt].no == 2) { // 두 가지 경우의 수
for (int i = 0; i < 2; i++) {
sel[cnt] = i;
DFS(cnt + 1);
}
}
else if(C[cnt].no == 5){ // 한 가지
sel[cnt] = 0;
DFS(cnt + 1);
}
else {
for (int i = 0; i < 4; i++) {
sel[cnt] = i;
DFS(cnt + 1);
}
}
}
Print_Sel()
원래 map을 손상시키지 않기 위해 tmpmap에 map을 복사해서 사용한다.
C[i].no 은 i번째 CCTV가 몇 번 CCTV인지 갖고 있다.
sel[i] 에는 i번째 CCTV가 가리키는 방향이 있다.
그러므로 dir[C[i].no][sel[i]] 이라는 1차원 배열에는 i번째 CCTV가 감시할 수 있는 방향이 동서남북으로 저장되어있다.
예를 들어, 1111 이라면 동서남북 다 감시할 수 있다는 뜻이다.
그럼 이제, CCTV위치를 기준으로 1인 방향들을 탐색하며 -1로 바꿔준다.
이때 CCTV이면 통과할 수 있으므로 이 칸에 -1을 체크하진 않되, while문을 게속 돌린다.
벽이면 통과 불가능이므로 그냥 while문에서 break해버린다.
while(ny >= 0 && ny < N && nx >= 0 && nx < M){
if (tmpmap[ny][nx] == 6) break; // 벽이면 막힘
if (tmpmap[ny][nx] == 0){
tmpmap[ny][nx] = -1;
}
ny += dy[j];
nx += dx[j];
}
모든 CCTV에 대해 tmpmap 표시를 했으면 이제 사각지대가 몇 칸이 남았는지 확인한다.
그냥 이중 for문으로 tmpmap을 탐색한다.
최종 코드
#include <stdio.h>
#include <string.h>
#define MAXMN 8
int N, M;
int map[MAXMN + 5][MAXMN + 5];
int tmpmap[MAXMN + 5][MAXMN + 5];
int dy[] = { 0, 0, 1, -1 }; //동서남북
int dx[] = { 1, -1, 0, 0 };
typedef struct st1 {
int y, x, no;
}CCTV;
CCTV C[8 + 2];
int ccnt;
int sel[8 + 2]; // 선택한 방향
int ans = 0x7fffffff;
// 방향
int dir[6][4][4] = {
{{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}, //1번 CCTV 동서남북 바라볼 때
{{1, 1, 0, 0}, {0, 0, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}}, // 2번, CCTV 동 / 남 바라볼 때
{{1, 0, 0, 1}, {1, 0, 1, 0}, {0, 1, 1, 0}, {0, 1, 0, 1}}, // 3번,
{{1, 1, 0, 1}, {1, 0, 1, 1}, {1, 1, 1, 0}, {0, 1, 1, 1}}, // 4번,
{{1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}} // 5번, 하나밖에 없음
};
void Get_Input(void) {
scanf("%d %d", &N, &M);
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
scanf("%d", &map[i][j]);
if (map[i][j] != 6 && map[i][j] != 0) { // CCTV 저장
C[ccnt].y = i, C[ccnt].x = j, C[ccnt++].no = map[i][j];
}
}
}
}
void Print_Sel(void) {
for (int i = 0; i < ccnt; i++) {
printf("%d ", sel[i]);
}
printf("\n");
}
int Try_Watch(void) {
//Print_Sel();
// 이 disSel 상황에서 감시하는 곳 표시
memset(tmpmap, 0, sizeof(tmpmap));
memcpy(tmpmap, map, sizeof(map));
for (int i = 0; i < ccnt; i++) {
int d = sel[i];
for (int j = 0; j < 4; j++) {
if (dir[C[i].no][d][j] == 0) continue;
int ny = C[i].y + dy[j];
int nx = C[i].x+dx[j];
while(ny >= 0 && ny < N && nx >= 0 && nx < M){
if (tmpmap[ny][nx] == 6) break; // 벽이면 막힘
if (tmpmap[ny][nx] == 0){
tmpmap[ny][nx] = -1;
}
ny += dy[j];
nx += dx[j];
}
}
}
int cnt = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (tmpmap[i][j] == 0) cnt++;
}
}
return cnt;
}
void DFS(int cnt) {
// 종료
if (cnt >= ccnt) {
// 다 골랐다.
int res = Try_Watch();
if (res < ans) ans = res;
return;
}
// idx번을 고를 차례
if (C[cnt].no == 2) { // 두 가지 경우의 수
for (int i = 0; i < 2; i++) {
sel[cnt] = i;
DFS(cnt + 1);
}
}
else if(C[cnt].no == 5){ // 한 가지
sel[cnt] = 0;
DFS(cnt + 1);
}
else {
for (int i = 0; i < 4; i++) {
sel[cnt] = i;
DFS(cnt + 1);
}
}
}
int main(void) {
Get_Input();
DFS(0);
printf("%d", ans);
return 0;
}
'컴퓨터기본 > 문제풀이' 카테고리의 다른 글
[백준] 14890번: 경사로 (0) | 2021.11.24 |
---|---|
[백준] 14891번: 톱니바퀴 (0) | 2021.11.24 |
[백준] 15684번: 사다리 조작 (0) | 2021.11.24 |
[백준] 14889번: 스타트와 링크 (0) | 2021.11.24 |
[백준] 15685번: 드래곤 커브 (0) | 2021.11.23 |