코딩 테스트(Coding Test)/백준

[백준] 17142번 : 연구소 3 - 자바(Java)

다문다뭉 2024. 11. 8. 22:38

Problem 🔒

문제

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

 

인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 바이러스는 활성 상태와 비활성 상태가 있다. 가장 처음에 모든 바이러스는 비활성 상태이고, 활성 상태인 바이러스는 상하좌우로 인접한 모든 빈 칸으로 동시에 복제되며, 1초가 걸린다. 승원이는 연구소의 바이러스 M개를 활성 상태로 변경하려고 한다.

연구소는 크기가 N×N인 정사각형으로 나타낼 수 있으며, 정사각형은 1×1 크기의 정사각형으로 나누어져 있다. 연구소는 빈 칸, 벽, 바이러스로 이루어져 있으며, 벽은 칸 하나를 가득 차지한다. 활성 바이러스가 비활성 바이러스가 있는 칸으로 가면 비활성 바이러스가 활성으로 변한다.

예를 들어, 아래와 같이 연구소가 생긴 경우를 살펴보자. 0은 빈 칸, 1은 벽, 2는 바이러스의 위치이다.

2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 2 0 1 1
0 1 0 0 0 0 0
2 1 0 0 0 0 2

 

M = 3이고, 바이러스를 아래와 같이 활성 상태로 변경한 경우 6초면 모든 칸에 바이러스를 퍼뜨릴 수 있다. 벽은 -, 비활성 바이러스는 *, 활성 바이러스는 0, 빈 칸은 바이러스가 퍼지는 시간으로 표시했다.

* 6 5 4 - - 2
5 6 - 3 - 0 1
4 - - 2 - 1 2
3 - 2 1 2 2 3
2 2 1 0 1 - -
1 - 2 1 2 3 4
0 - 3 2 3 4 *

 

시간이 최소가 되는 방법은 아래와 같고, 4초만에 모든 칸에 바이러스를 퍼뜨릴 수 있다.

0 1 2 3 - - 2
1 2 - 3 - 0 1
2 - - 2 - 1 2
3 - 2 1 2 2 3
3 2 1 0 1 - -
4 - 2 1 2 3 4
* - 3 2 3 4 *

 

연구소의 상태가 주어졌을 때, 모든 빈 칸에 바이러스를 퍼뜨리는 최소 시간을 구해보자.

입력

첫째 줄에 연구소의 크기 N(4 ≤ N ≤ 50), 놓을 수 있는 바이러스의 개수 M(1 ≤ M ≤ 10)이 주어진다.

둘째 줄부터 N개의 줄에 연구소의 상태가 주어진다. 0은 빈 칸, 1은 벽, 2는 비활성 바이러스의 위치이다. 2의 개수는 M보다 크거나 같고, 10보다 작거나 같은 자연수이다.

출력

연구소의 모든 빈 칸에 바이러스가 있게 되는 최소 시간을 출력한다. 바이러스를 어떻게 놓아도 모든 빈 칸에 바이러스를 퍼뜨릴 수 없는 경우에는 -1을 출력한다.

테스트 케이스 보기

예제 입력 1

7 3
2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 2 0 1 1
0 1 0 0 0 0 0
2 1 0 0 0 0 2

예제 출력 1

4

예제 입력 2

7 3
2 0 2 0 1 1 0
0 0 1 0 1 2 0
0 1 1 2 1 0 0
2 1 0 0 0 0 2
0 0 0 2 0 1 1
0 1 0 0 0 0 0
2 1 0 0 2 0 2

예제 출력 2

4

예제 입력 3

7 4
2 0 2 0 1 1 0
0 0 1 0 1 2 0
0 1 1 2 1 0 0
2 1 0 0 0 0 2
0 0 0 2 0 1 1
0 1 0 0 0 0 0
2 1 0 0 2 0 2

예제 출력 3

4

예제 입력 4

7 5
2 0 2 0 1 1 0
0 0 1 0 1 2 0
0 1 1 2 1 0 0
2 1 0 0 0 0 2
0 0 0 2 0 1 1
0 1 0 0 0 0 0
2 1 0 0 2 0 2

예제 출력 4

3

예제 입력 5

7 3
2 0 2 0 1 1 0
0 0 1 0 1 0 0
0 1 1 1 1 0 0
2 1 0 0 0 0 2
1 0 0 0 0 1 1
0 1 0 0 0 0 0
2 1 0 0 2 0 2

예제 출력 5

7

예제 입력 6

7 2
2 0 2 0 1 1 0
0 0 1 0 1 0 0
0 1 1 1 1 0 0
2 1 0 0 0 0 2
1 0 0 0 0 1 1
0 1 0 0 0 0 0
2 1 0 0 2 0 2

예제 출력 6

-1

예제 입력 7

5 1
2 2 2 1 1
2 1 1 1 1
2 1 1 1 1
2 1 1 1 1
2 2 2 1 1

예제 출력 7

0

 


Approach ⭕

  1. 연구소 상태 입력 받기 및 초기 처리
    • 입력 받으면서 바이러스의 위치(2)를 10*2 배열(virus)에 저장한다.
    • 바이러스(M)의 개수는 최대 10개이므로 배열을 미리 만들어 두고 저장했다.
    • 입력 받으면서 바이러스가 모두 확산되기 위해 필요한 빈 칸(0)의 개수를 저장한다.
    • 빈 칸의 개수가 0개라면 0초를 출력한다.
  2. 조합을 활용한 바이러스 선택(Combi 메서드)
    • 바이러스 총 개수(T) 중에서 M개의 바이러스를 선택하는 메서드이다.
    • depth는 크기가 pick 배열에 저장할 인덱스이며, 현재 선택한 바이러스의 개수를 나타낸다.
    • start는 바이러스의 위치를 저장한 10*2 배열(virus)의 행 인덱스를 나타낸다.
    • 최종적으로, pick 배열에는 virus 배열의 행 인덱스를 M개 저장하게 된다.
  3. BFS를 활용한 바이러스 확산(Spread 메서드)
    • 현재 맵에 있는 빈 칸의 개수를 인자로 받게 한다.
    • 방문 배열(visit), 확산되는 데 걸린 시간(sec), 큐를 초기화한다.
    • pick 배열에 저장된 M개의 바이러스 위치를 꺼내 방문 처리하고, 큐에 추가한다.
    • 현재 초에서 확산할 바이러스의 수는 큐의 크기이다.
    • 현재 큐의 크기만큼 루프를 돌면서, 큐에서 바이러스의 위치를 꺼낸다.
    • 꺼낸 바이러스의 상하좌우 인접한 칸을 탐색하며, 경계를 벗어나지 않고, 벽이 아니며, 방문하지 않았으면 바이러스를 퍼트린다.
    • 바이러스를 퍼트리는 데 성공했으면 방문 처리하고, 큐에 바이러스 위치를 넣고, 해당 위치가 빈 칸이라면 빈 칸의 개수를 하나 감소시킨다. (비활성화된 바이러스인 경우에는 감소시키면 안된다)
    • 현재 초에서 확산할 바이러스의 수만큼 루프를 다 돌았다면, 걸린 시간(sec)을 증가시킨다.
    • 빈 칸의 개수가 0이라면 바이러스가 모두 확산되었다는 뜻이므로, 최소 시간(ans)을 갱신해주고 루프를 종료한다.
    • 빈 칸의 개수가 남아있는데, 큐의 크기가 0이라면 더이상 바이러스가 퍼질 수 없다는 뜻이므로, 루프를 종료한다.
  4. 출력
    • 모든 경우의 수를 검토한 후 최소 시간이 초기 값과 같다면, 모든 빈 칸에 바이러스를 퍼뜨릴 수 없는 경우이므로 -1을 출력하고, 아니라면 최소 시간을 출력한다.

Solution 💡

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class 연구소3 {
    static int N, M, K, Z=0, T=0, ans=10000; // Z:빈 칸 개수, T:바이러스 총 개수
    static int[][] map, visit, virus=new int[10][2];
    static int[] dr={0,0,1,-1}, dc={1,-1,0,0}, pick;
    static Queue<int[]> q = new LinkedList<>();
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt();
        M = sc.nextInt();
        map = new int[N][N];
        visit = new int[N][N];
        pick = new int[M]; // 조합으로 뽑은 M개의 바이러스

        for(int i=0; i<N; i++){
            for(int j=0; j<N; j++){
                K = sc.nextInt();
                map[i][j] = K;
                if(K==2){virus[T][0]=i; virus[T++][1]=j;}
                if(K==0) Z++;
            }
        }
        if(Z==0){System.out.println(0); return;}
        Combi(0,0);
        System.out.println(ans==10000?-1:ans);

    }

    public static void Combi(int depth, int start){
        if(depth==M){
            Spread(Z);
            return;
        }
        for(int i=start; i<T; i++){
            pick[depth] = i;
            Combi(depth+1, i+1);
        }
    }//Combi

    public static void Spread(int zero){
        visit = new int[N][N];
        int sec=0;
        q.clear();
        // 뽑은 바이러스 방문처리, 큐에 넣기
        for(int i=0; i<M; i++){
            int r = virus[pick[i]][0]; // 뽑은 바이러스의 행 인덱스
            int c = virus[pick[i]][1]; // 뽑은 바이러스의 열 인덱스
            visit[r][c] = 1;
            q.add(new int[]{r,c});
        }

        while (true){
            int size = q.size();
            for(int i=0; i<size; i++){
                int[] get = q.poll();
                for(int d=0; d<4; d++){
                    int nr = get[0] + dr[d];
                    int nc = get[1] + dc[d];

                    if(nr>=0 && nr<N && nc>=0 && nc<N && map[nr][nc]!=1 && visit[nr][nc]!=1){
                        visit[nr][nc]=1;
                        if(map[nr][nc]==0) zero--;
                        q.add(new int[]{nr,nc});
                    }
                }
            }
            sec++;
            if(zero==0){ans=Math.min(ans,sec); break;}
            if(size==0) break;
        }

    }//Spread
}


의문점 ⁉️

  1. 큐 초기화하는 이유
    • 새로운 조합의 바이러스 위치에서 BFS(Spread 메서드)가 실행될 때마다, 각각의 조합이 독립적으로 실행되야한다.
    • 빈 칸의 개수(zero)가 0이 된 순간에 루프는 종료되지만, 아직 큐에 남은 요소가 있을 수 있다.
  2. 방문 배열을 새로 만드는 방식으로 초기화한 이유
    • 성능 관점에서 방문 배열을 유지하면서 요소만 0으로 초기화 할 수도 있었지만, N이 최대 50이기 때문에 이 정도 크기의 배열은 자주 초기화하더라도 큰 부담이 없을 것이라 생각했다.