coding test

[파이썬, Java] 15683. 감시

잔망루피 2021. 9. 30. 19:55

문제

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

↑→ ←↑→ ←↕→
1번 2번 3번 4번 5번

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

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

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

0 0 0 0 0 0

0 0 0 0 0 0

0 0 1 0 6 0

0 0 0 0 0 0

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

0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 # 6 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
# # 1 0 6 0
0 0 0 0 0 0
0 0 # 0 0 0
0 0 # 0 0 0
0 0 1 0 6 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 0 6 0
0 0 # 0 0 0

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

0 0 0 0 0 0

0 2 0 0 0 0

0 0 0 0 6 0

0 6 0 0 2 0

0 0 0 0 0 0

0 0 0 0 0 5

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

0 0 0 0 0 #
# 2 # # # #
0 0 0 0 6 #
0 6 # # 2 #
0 0 0 0 0 #
# # # # # 5
0 0 0 0 0 #
# 2 # # # #
0 0 0 0 6 #
0 6 0 0 2 #
0 0 0 0 # #
# # # # # 5
0 # 0 0 0 #
0 2 0 0 0 #
0 # 0 0 6 #
0 6 # # 2 #
0 0 0 0 0 #
# # # # # 5
0 # 0 0 0 #
0 2 0 0 0 #
0 # 0 0 6 #
0 6 0 0 2 #
0 0 0 0 # #
# # # # # 5
왼쪽 상단 2: ↔,
오른쪽 하단 2: ↔
왼쪽 상단 2: ↔,
오른쪽 하단 2: ↕
왼쪽 상단 2: ↕,
오른쪽 하단 2: ↔
왼쪽 상단 2: ↕,
오른쪽 하단 2: ↕

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

0 0 2 0 3

0 6 0 0 0

0 0 6 6 0

0 0 0 0 0

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

# # 2 # 3

0 6 # 0 #

0 0 6 6 #

0 0 0 0 #

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

입력

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

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

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

출력

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

예제 입력 1 

4 6

0 0 0 0 0 0

0 0 0 0 0 0

0 0 1 0 6 0

0 0 0 0 0 0

예제 출력 1 

20

예제 입력 2 

6 6

0 0 0 0 0 0

0 2 0 0 0 0

0 0 0 0 6 0

0 6 0 0 2 0

0 0 0 0 0 0

0 0 0 0 0 5

예제 출력 2 

15

예제 입력 3 

6 6

1 0 0 0 0 0

0 1 0 0 0 0

0 0 1 0 0 0

0 0 0 1 0 0

0 0 0 0 1 0

0 0 0 0 0 1

예제 출력 3 

6

예제 입력 4 

6 6

1 0 0 0 0 0

0 1 0 0 0 0

0 0 1 5 0 0

0 0 5 1 0 0

0 0 0 0 1 0

0 0 0 0 0 1

예제 출력 4 

2

예제 입력 5 

1 7

0 1 2 3 4 5 6

예제 출력 5 

0

예제 입력 6 

3 7

4 0 0 0 0 0 0

0 0 0 2 0 0 0

0 0 0 0 0 0 4

예제 출력 6 

0

 

 

🎀 나의 풀이

# 통과
from collections import deque
import sys, copy

input=sys.stdin.readline
N, M=map(int, input().split())      # 세로 크기, 가로 크기
dx=[1, 0, -1, 0]
dy=[0, -1, 0, 1]
total=N*M
matrix=list()
cctv=[]     # cctv 튜플 위치
cctv5=[]    # 5번 cctv 튜플 위치
que=deque()     # 방향을 담은 deque
cctv_cnt=0

for i in range(N) :
    matrix.append(list(map(int, input().split())))
    for j in range(M) :
        if 0 < matrix[i][j] < 5 :
            cctv.append((i, j))
            cctv_cnt+=1
        elif matrix[i][j] == 6 :  # 벽
            total-=1
        elif matrix[i][j] == 5 :      # 5
            cctv5.append((i, j))
            total-=1

total-=cctv_cnt


def move(x, y, i) :
    cnt=0   # 감시되는 구간 갯수
    while True:
        nx = x + dx[i]
        ny = y + dy[i]
        if 0 <= nx < N and 0 <= ny < M :
            if copied[nx][ny] == 6:  # 벽
                return cnt
            if 0 < copied[nx][ny] < 6 or copied[nx][ny] == -1 :  # cctv 또는 감시된 구역
                x = nx
                y = ny
                continue
            copied[nx][ny] = -1     # 감시
            cnt += 1
            x = nx
            y = ny
        else :
            return cnt


def play(cnt) :     # cnt=cctv 개수
    global copied, answer
    if cnt == cctv_cnt :        # 모든 cctv(5번 제외)
        copied = copy.deepcopy(matrix)
        result=0       # -1의 갯수(감시된 구역)
        for i in range(cctv_cnt) :
            x, y=cctv[i]
            if matrix[x][y] == 1 :   # → / ↓ / ← / ↑
                result+=move(x, y, que[i])
            elif matrix[x][y] == 2 :     # ↔ / ↕
                result+=move(x, y, que[i])
                result+=move(x, y, (que[i]+2)%4)
            elif matrix[x][y] == 3 :    # ㄴ / 하+우 / 상+좌
                result+=move(x, y, que[i])
                result+=move(x, y, (que[i]+1)%4)
            else :          # ㅗ / ㅏ / ㅜ / ㅓ
                result+=move(x, y, que[i])
                result+=move(x, y, (que[i]+1)%4)
                result+=move(x, y, (que[i]+2)%4)

        answer=min(total-result, answer)
        return

    for i in range(4) : # 4가지 방향
        que.append(i)
        play(cnt+1)
        que.pop()


# 5번 CCTV 감시
result=0
copied=matrix
for i in cctv5 :
    for j in range(4) : #네방향
        result+=move(i[0], i[1], j)

total-=result
answer=total
play(0)
print(answer)

❗ 내가 이전에 통과하지 못한 이유

1. # 대신 -1로 하는 게 좋다. 타입에러 떴었다.

if 0 < matrix[nx][ny] < 6 and matrix[nx][ny] == "#" 같은 경우에서는 0 < matrix[nx][ny] < 6에서 타입에러가 뜬다.

2. 방향

상하좌우로 했는데 x

'하좌상우' 이런식으로 반시계든 시계방향이든 이어져야 함

3. cctv 번호 또는 감시 구역을 건너띄어야 함

조건식에서 matrix[nx][ny] == -1을 빼먹음

4. in 대신 비교문 쓰기

if matrix[nx][ny] in range(1, 6) 이런식으로 하니까 시간 초과가 뜬다.

in은 시간 복잡도가 O(N)

if 0 < matrix[nx][ny] < 6로 바꿨다.

 

 

import java.io.*;
import java.util.*;

class Pos{
    int x;
    int y;
    Pos(int x, int y){
        this.x=x;
        this.y=y;
    }
}

public class Main{
    static int N;   // 세로
    static int M;   // 가로
    static int[][] arr;
    static int[][] copy;
    static ArrayList<Pos> cctv5=new ArrayList<>();
    static ArrayList<Pos> cctv=new ArrayList<>();
    static ArrayList<Integer> tmp=new ArrayList<>();
    static int total;
    static int ans;
    static int c;
    /* 좌 상 우 하
    static int[] dx={0, -1, 0, 1};
    static int[] dy={-1, 0, 1, 0};*/

    static int[] dx={1, 0, -1, 0};
    static int[] dy={0, -1, 0, 1};

    public static void main(String[] args) throws Exception {
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st=new StringTokenizer(br.readLine());
        N=Integer.parseInt(st.nextToken());
        M=Integer.parseInt(st.nextToken());
        arr=new int[N][M];
        copy=new int[N][M];
        total=N*M;          // 넓이
        int tmp=0;
        // 배열에 값 입력
        for(int i=0; i<N; i++){
            st=new StringTokenizer(br.readLine());
            for(int j=0; j<M; j++){
                tmp=Integer.parseInt(st.nextToken());
                if(tmp == 5){
                    cctv5.add(new Pos(i, j));
                    total--;
                }else if(0 < tmp && tmp < 5){       // cctv
                    cctv.add(new Pos(i, j));
                    total--;
                }else if(tmp == 6){     // 벽
                    total--;
                }
                arr[i][j]=tmp;
            }
        }
        // cctv 번호5부터 처리
        for(Pos p : cctv5){
            int x=p.x;
            int y=p.y;
            for(int i=0; i<4; i++){
                int nx=x;
                int ny=y;
                while(true){
                    nx+=dx[i];
                    ny+=dy[i];
                    if((0 > nx || nx >= N) || (0 > ny || ny >= M) || arr[nx][ny] == 6){     // 범위를 넘거나 벽이면 break
                        break;
                    }else if((0 < arr[nx][ny] && arr[nx][ny] <= 5) || arr[nx][ny] == -1){      // cctv 번호(1~5)라면 통과(건너띄기)
                        continue;
                    }
                    arr[nx][ny]=-1;
                    total--;
                }
            }
        }
        ans=total;
        dfs(0);
        System.out.println(ans);
    }

    public static int move(int x, int y, int d){
        int c=0;
        int nx=x;
        int ny=y;
        while(true){
            nx=x+dx[d];
            ny=y+dy[d];
            if((0 > nx || nx >= N) || (0 > ny || ny >= M) || copy[nx][ny] == 6){
                return c;
            }else if((0 < copy[nx][ny] && copy[nx][ny] <= 5 ) || copy[nx][ny] == -1){
                x=nx;
                y=ny;
                continue;
            }
            copy[nx][ny]=-1;
            c++;
            x=nx;
            y=ny;
        }

    }

    public static void dfs(int cnt){
        if(cnt == cctv.size()){
            // 깊은 복사
            for(int i=0; i<N; i++){
                System.arraycopy(arr[i], 0, copy[i], 0, M);
            }
            c=0;
            for(int j=0; j<cctv.size(); j++){
                int x=cctv.get(j).x;
                int y=cctv.get(j).y;
                if(copy[x][y] == 1){
                    c+=move(x, y, tmp.get(j));
                }
                else if(copy[x][y] == 2){
                    c+=move(x, y, tmp.get(j));
                    c+=move(x, y, (tmp.get(j)+2)%4);
                }
                else if(copy[x][y] == 3){
                    c+=move(x, y, tmp.get(j));
                    c+=move(x, y, (tmp.get(j)+1)%4);
                }
                else{
                    c+=move(x, y, tmp.get(j));
                    c+=move(x, y, (tmp.get(j)+1)%4);
                    c+=move(x, y, (tmp.get(j)+2)%4);
                }
            }
            ans=Math.min(ans, total-c);
            return;
        }

        for(int i=0; i<4; i++){
            tmp.add(i);
            dfs(cnt+1);
            tmp.remove(tmp.size()-1);
        }
    }
}

다른 사람의 파이썬 풀이를 자바로 구현

문제의 4번, 5번 케이스가 음수로 나온다 🥲

어디가 문제인지 모르겠다 ㅠ

나중에 다시 봐야지..

5번 cctv 감시할 때 if문에 arr[nx][ny] == -1 추가하고 해결

else if((0 < arr[nx][ny] && arr[nx][ny] <= 5) || arr[nx][ny] == -1){      // cctv 번호(1~5)라면 통과(건너띄기)
    continue;
}

 

 

# 실패
import sys
import copy
from collections import defaultdict

input=sys.stdin.readline

# 0 : 빈 칸, 6 : 벽, 1~5 : CCTV
answer=64
N, M=map(int, input().split())    # 세로, 가로
dic=defaultdict(list)
pos={}
pos[1]=[(0, 1), (0, -1), (1, 0), (-1, 0)]
pos[2]=[(0, -1, 0, 1)]

room=[list() for n in range(N)]
for n in range(N) :
    room[n]=list(map(int, input().split()))

# CCTV 위치찾기
for n in range(N) :
    if 1 in room[n] :
        dic[1].append((n, room[n].index(1)))
    if 2 in room[n] :
        dic[2].append((n, room[n].index(2)))
    if 3 in room[n] :
        dic[3].append((n, room[n].index(3)))
    if 4 in room[n] :
        dic[4].append((n, room[n].index(4)))
    if 5 in room[n] :
        dic[5].append((n, room[n].index(5)))

def cnt_0(room) :
    result=0
    for n in range(N) :
        result+=room[n].count(0)
    return result

def cctv(kind, x, y, p, temp_room):
    nx = p[0] + x
    ny = p[1] + y
    if 0 <= nx < N and 0 <= ny < M:
        if temp_room[nx][ny] == 0:
            temp_room[nx][ny] = "#"  # 감시
            cctv(kind, nx, ny, p, temp_room)

for i in dic.keys() :
    for j in dic[i] :
        #for p in pos[i] :
        temp_room = copy.deepcopy(room)
        if i == 1 :
            for p in (-1, 0), (1, 0), (0, -1), (0, 1) :
                cctv(i, j[0], j[1], p, temp_room)
        if i == 2 :
            for p in (0, -1), (0, 1) :      # <->
                cctv(i, j[0], j[1], p, temp_room)
        answer = min(answer, cnt_0(temp_room))
        print(temp_room)
print(answer)

문제의 예시처럼 cctv 2개가 2일 경우 4가지를 고려해야 하는데 이렇게 풀면 안 된다.

재귀가 필요하다.

 

 

🐊 다른 사람 풀이

# https://chldkato.tistory.com/152
from collections import deque
from copy import deepcopy
import sys

input=sys.stdin.readline
dx=[1, 0, -1, 0]		# 남서북동
dy=[0, -1, 0, 1]

def dfs(cnt) :
    global ans, temp_a
    if cnt == len(cctv) :
        temp_a=deepcopy(a)
        c=0
        for i in range(len(cctv)) :
            x,y=cctv[i]
            if a[x][y] == 1 :
                c+=move(x, y, dir[i])
            elif a[x][y] == 2 :
                c+=move(x, y, dir[i])
                c+=move(x, y, (dir[i]+2)%4)
            elif a[x][y] == 3 :
                c+=move(x, y, dir[i])
                c+=move(x, y, (dir[i]+1)%4)
            else :
                c+=move(x, y, dir[i])
                c+=move(x, y, (dir[i]+1)%4)
                c+=move(x, y, (dir[i]+2)%4)
        ans=min(ans, area-c)
        return

    for i in range(4) :
        dir.append(i)
        dfs(cnt+1)
        dir.pop()

def move(x, y, d) :
    cnt=0
    while True :
        nx=x+dx[d]
        ny=y+dy[d]
        if not 0 <= nx < n or not 0 <= ny < m or temp_a[nx][ny] == 6 :
            return cnt
        if 0 < temp_a[nx][ny] < 6 or temp_a[nx][ny] == -1 :
            x, y=nx, ny
            continue
        temp_a[nx][ny]=-1
        cnt+=1
        x, y=nx, ny

n, m=map(int, input().split())
area=n*m
a, cctv, cctv5=[], [], []
for i in range(n) :
    row=list(map(int, input().split()))
    a.append(row)
    for j in range(m) :
        if 0 < a[i][j] < 5 :
            cctv.append([i, j])
            area-=1
        elif a[i][j] == 5 :
            cctv5.append([i, j])
            area-=1
        elif a[i][j] == 6 :
            area-=1

for i in range(len(cctv5)) :
    x, y=cctv5[i]
    for i in range(4) :
        nx, ny=x, y
        while True :
            nx+=dx[i]
            ny+=dy[i]
            if not 0 <= nx < n or not 0 <= ny < m or a[nx][ny] == 6 :
                break
            if 0 < a[nx][ny] < 6 or a[nx][ny] == -1 :
                continue
            a[nx][ny]=-1
            area-=1

dir=deque()
ans=area
dfs(0)
print(ans)

백트래킹

cctv 리스트에 1부터 4까지 cctv의 위치를 넣는다.

cctv5 리스트에 5번 cctv 위치를 넣는다.

5번 cctv부터 시작한다.

x 좌표와 y 좌표가 범위를 넘거나 a[nx][ny]가 6이면(벽) break한다.

a[nx][ny]가 1~5번 cctv거나 감시한 구역 -1이면 넘어간다.

위 두가지에 해당하지 않으면 감시한 구역 -1로 하고, area를 1 감소시킨다.

dfs 함수는 cctv의 번호에 따라 move 함수를 다르게 호출한다.

move 함수는 감시 가능한 칸의 갯수를 반환한다.

area=min(area, area-c)로 쓰는 실수를 할 수 있다. 

area는 최소값으로 계속 갱신되기 때문에 5번 cctv까지 처리한 ans가 초기 사각지대 값이다.

 

 

import sys
input=sys.stdin.readline

def watch(x, y, direction) :
    ret=set()
    for d in direction :    # 0~3 : 북, 동, 남, 서
        new_x, new_y=x+dx[d], y+dy[d]
        while 0 <= new_x < N and 0 <= new_y < M :
            if office[new_x][new_y] == 6 : break
            if office[new_x][new_y] == 0 : ret.add((new_x, new_y))
            new_x, new_y=new_x+dx[d], new_y+dy[d]

    return ret

def dfs(n, watched_set) :
    global max_watched_cnt
    if n == len(cctvs_cases) :
        if max_watched_cnt < len(watched_set) :
            max_watched_cnt=len(watched_set)
        return
    for cctv_cases in cctvs_cases[n] :
        dfs(n+1, watched_set | cctv_cases)

cctvs_cases=[]      # 각 cctv의 가능한 케이스

dx, dy=(-1, 0, 1, 0), (0, 1, 0, -1)     # 북동남서, 좌표 이동 벡터

N, M=map(int, input().split())      # N: 세로, M: 가로
office=[[*map(int, input().split())] for _ in range(N)] # cctv, 벽 정보

will_watched_cnt=0
for idx in range(N*M) :
    i, j=divmod(idx, M)
    if office[i][j] == 0 :
        will_watched_cnt+=1
    elif office[i][j] == 1 :
        cctvs_cases.append([watch(i, j, [v]) for v in range(4)])
    elif office[i][j] == 2 :
        cctvs_cases.append([watch(i, j, [v%4, (v+2)%4]) for v in range(2)])
    elif office[i][j] == 3 :
        cctvs_cases.append([watch(i, j, [v%4, (v+1)%4]) for v in range(4)])
    elif office[i][j] == 4 :
        cctvs_cases.append([watch(i, j, [(_ + v)%4 for _ in range(3)]) for v in range(4)])
    elif office[i][j] == 5 :
        cctvs_cases.append([watch(i, j, [0, 1, 2, 3])])

max_watched_cnt=0
dfs(0, set())       # 감시 가능한 최대 영역 개수

print(will_watched_cnt - max_watched_cnt)       # 최소 사각지대 개수

cctvs_cases 리스트에 1번 부터 5번까지 위치와 방향을 넣었다.

watch  메소드에서 n번 cctv에서 시작해 direction 방향으로 가면서 본 0인 구역을 set에 담아 반환

미리 watch 메소드에 가능한 케이스를 다 담은 후에 dfs 메소드 호출

dfs 메소드에서 cctv_case개가 될때 까지 set을 or 연산으로 추가한다.

이전의 백트래킹 방식보다 훨씬 더 효율적이다.

 

 

문제 출처 👉 백준

 

 

 

 

 

 

 

 

반응형