
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 ⭕
- 연구소 상태 입력 받기 및 초기 처리
- 입력 받으면서 바이러스의 위치(2)를 10*2 배열(virus)에 저장한다.
- 바이러스(M)의 개수는 최대 10개이므로 배열을 미리 만들어 두고 저장했다.
- 입력 받으면서 바이러스가 모두 확산되기 위해 필요한 빈 칸(0)의 개수를 저장한다.
- 빈 칸의 개수가 0개라면 0초를 출력한다.
- 조합을 활용한 바이러스 선택(Combi 메서드)
- 바이러스 총 개수(T) 중에서 M개의 바이러스를 선택하는 메서드이다.
- depth는 크기가 pick 배열에 저장할 인덱스이며, 현재 선택한 바이러스의 개수를 나타낸다.
- start는 바이러스의 위치를 저장한 10*2 배열(virus)의 행 인덱스를 나타낸다.
- 최종적으로, pick 배열에는 virus 배열의 행 인덱스를 M개 저장하게 된다.
- BFS를 활용한 바이러스 확산(Spread 메서드)
- 현재 맵에 있는 빈 칸의 개수를 인자로 받게 한다.
- 방문 배열(visit), 확산되는 데 걸린 시간(sec), 큐를 초기화한다.
- pick 배열에 저장된 M개의 바이러스 위치를 꺼내 방문 처리하고, 큐에 추가한다.
- 현재 초에서 확산할 바이러스의 수는 큐의 크기이다.
- 현재 큐의 크기만큼 루프를 돌면서, 큐에서 바이러스의 위치를 꺼낸다.
- 꺼낸 바이러스의 상하좌우 인접한 칸을 탐색하며, 경계를 벗어나지 않고, 벽이 아니며, 방문하지 않았으면 바이러스를 퍼트린다.
- 바이러스를 퍼트리는 데 성공했으면 방문 처리하고, 큐에 바이러스 위치를 넣고, 해당 위치가 빈 칸이라면 빈 칸의 개수를 하나 감소시킨다. (비활성화된 바이러스인 경우에는 감소시키면 안된다)
- 현재 초에서 확산할 바이러스의 수만큼 루프를 다 돌았다면, 걸린 시간(sec)을 증가시킨다.
- 빈 칸의 개수가 0이라면 바이러스가 모두 확산되었다는 뜻이므로, 최소 시간(ans)을 갱신해주고 루프를 종료한다.
- 빈 칸의 개수가 남아있는데, 큐의 크기가 0이라면 더이상 바이러스가 퍼질 수 없다는 뜻이므로, 루프를 종료한다.
- 출력
- 모든 경우의 수를 검토한 후 최소 시간이 초기 값과 같다면, 모든 빈 칸에 바이러스를 퍼뜨릴 수 없는 경우이므로 -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
}
의문점 ⁉️
- 큐 초기화하는 이유
- 새로운 조합의 바이러스 위치에서 BFS(Spread 메서드)가 실행될 때마다, 각각의 조합이 독립적으로 실행되야한다.
- 빈 칸의 개수(zero)가 0이 된 순간에 루프는 종료되지만, 아직 큐에 남은 요소가 있을 수 있다.
- 방문 배열을 새로 만드는 방식으로 초기화한 이유
- 성능 관점에서 방문 배열을 유지하면서 요소만 0으로 초기화 할 수도 있었지만, N이 최대 50이기 때문에 이 정도 크기의 배열은 자주 초기화하더라도 큰 부담이 없을 것이라 생각했다.
'코딩 테스트(Coding Test) > 백준' 카테고리의 다른 글
[백준] 14442번 : 벽 부수고 이동하기 2 - 자바(Java) (0) | 2024.11.10 |
---|---|
[백준] 2206번 : 벽 부수고 이동하기 - 자바(Java) (1) | 2024.11.09 |
[백준] 17141번 : 연구소 2 - 자바(Java) (1) | 2024.11.08 |
[백준] 7682번 : 틱택토 - 자바(Java) (0) | 2024.11.06 |
[백준] 4179번 : 불! - 자바(Java) (0) | 2024.11.06 |