[백준] 17141. 연구소 2 (골드4) - DFS, BFS

Kiefer Sol·2024년 8월 12일

알고리즘

목록 보기
29/35

17141. 연구소 2 (골드4)

시간 제한메모리 제한제출정답맞힌 사람정답 비율
1 초512 MB116114873349743.932%

문제

인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 승원이는 연구소의 특정 위치에 바이러스 M개를 놓을 것이고, 승원이의 신호와 동시에 바이러스는 퍼지게 된다.

연구소는 크기가 N×N인 정사각형으로 나타낼 수 있으며, 정사각형은 1×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 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 5

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

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
5 - 3 2 3 4 5

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

입력

첫째 줄에 연구소의 크기 N(5 ≤ 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
5

예제 입력 2
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

예제 출력 2
4

* 바이러스 위치가 고정된 것이 아니라, 바이러스를 놓을 수 있는 자리를 알려줌, 바이러스가 놓기 전 자리는 빈칸과 같은 취급

* 모든 빈칸에 바이러스가 놓아지는 최소 시간

풀이

  1. 입력받을 때 바이러스 존이면 list에 넣기, 바이러스를 놓을 수 있는 자리 확인하는 check 배열 초기화
  2. DFS에서 조합을 활용해서 nCr
    2.1 내려가면서 check 배열에 1과 0넣어가면서 M까지 내려가기
    2.2 바이러스 갯수가 M에 도달했을 때, 이제 BFS 호출해 바이러스 확산 확인하기
  3. BFS로 바이러스 확산 확인
    3.1. 현재 위치 배열에 영향을 끼치지 않게 깊은 배열 복사로, 복사 배열을 형성한다.
    3.2. 바이러스 넣은 곳 큐에 넣고, 방문(visted)배열 체크
    3.3. 사방돌면서 복사배열이 빈칸(0)이거나, 바이러스를 넣을 수 있는 자리(2)이고, 방문을 안했으면 => 시간증가, 큐에 넣기, 복사위치배열 바이러스 감염체크(3으로 수정), 방문배열 체크
  4. BFS 끝난 후 배열돌면서, 빈칸(0)이거나, 바이러스를 넣을 수 있는 자리(2)가 남아 있으면 return(확산x), 아니면 시간 배열 중 최댓값구하기
  5. 시간배열 최댓값들 중에서 최솟값 구하기
  • check배열 - 바이러스를 놓은 자리, visited - 바이러스를 실제 넣고, 감염된 자리
public class Search_17141 {
	public static int N, M;
	public static int[][] arr;
	public static ArrayList<Point> list;
	public static int[] check; // 바이러스를 놓을 수 있는 자리
	public static int[] dx = { -1, 1, 0, 0 };
	public static int[] dy = { 0, 0, -1, 1 };
	public static int minTime = Integer.MAX_VALUE;

	public static class Point {
		public int x;
		public int y;
		public Point(int x, int y) {
			this.x = x;
			this.y = y;
		}
		
	}

	public static void main(String[] args) throws IOException {
		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][N];
		list = new ArrayList<Point>();

		for (int i = 0; i < N; i++) {
			st = new StringTokenizer(br.readLine());
			for (int j = 0; j < N; j++) {
				arr[i][j] = Integer.parseInt(st.nextToken());
                
                // 바이러스를 놓을 수 있는 지리면 list에 넣기
				if(arr[i][j]==2) list.add(new Point(i,j));
			}
		}
        // 바이러스를 놓을 수 있는 자리 확인 
		check = new int[list.size()];
		
		DFS(0, 0);

		if(minTime==Integer.MAX_VALUE) System.out.println(-1);
		else System.out.println(minTime);

	}
	
	public static void DFS(int start, int cnt) {
		//먼저 바이러스를 놓을 수 있는 k개의 위치 중 m개의 위치를 선정해야 한다. 이는 조합으로 나타낼 수 있다.
		if(cnt==M) {
			BFS(); //바이러스 확산 검사
			return;
		}
        	// DFS에서 조합을 활용해서 nCr, 내려가면서 check 배열에 1과 0넣어가면서 M까지 내려가기
			for(int i=start; i<list.size(); i++) {
				check[i] = 1; //바이러스 설치
	            DFS(i + 1, cnt+1);
	            check[i] = 0; //바이러스 미설치
			}
		}
	}
	
	public static void BFS() {
		// 깊은 복사
		int[][] copy_arr = new int[N][N];
		for (int i = 0; i < N; i++) {
			copy_arr[i] = arr[i].clone();
        }
        // 바이러스를 놓을 수 있는 자리들 중에 실제로 바이러스를 넣을 위치 선정
		for(int i=0; i<list.size(); i++) {
			if(check[i]==1) {
				Point po = list.get(i);
                // 바이러스 넣기
				copy_arr[po.x][po.y]=3;
			}
		}
		int[][] time = new int[N][N]; // 각 칸에 바이러스 퍼지기 위한 시간 계산
		int[][] visited = new int[N][N]; // 각 칸에 바이러스가 점염됬는지 여부 파악
		Queue<Point> que = new LinkedList<>();
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if(copy_arr[i][j]==3) { // 실제 바이러스 있는자리
					que.offer(new Point(i,j));
					visited[i][j]=1; //확산
				}
			}
		}
		while(!que.isEmpty()) {
            Point now = que.poll();
            int x = now.x; 
            int y = now.y; 

            for(int k=0; k<4; k++) {
                int nx = x + dx[k];
                int ny = y + dy[k];
                if(0<=nx && nx<N && 0<=ny && ny<N) {
                	// 벽이 아니거나, 아직 바이러스가 확산되지 않은 장소
                    if((copy_arr[nx][ny] == 0||copy_arr[nx][ny] == 2) && visited[nx][ny]==0) {
                    	time[nx][ny] = time[x][y]+1; // 다음 위치는 현재 위치에서 한번더 이동해야 한다.
                    	if(time[nx][ny]>minTime) return; //현재 최소 시간보다 큰 곳은 굳이 방문할 필요 없다 - 시간절약
                        que.offer(new Point(nx,ny));
                        copy_arr[nx][ny] = 3; //실제 바이러스 점염
                        visited[nx][ny]=1; // 바이러스 점염
                        
                    }
                }
            }
        }

		int maxtime =0;
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if(copy_arr[i][j]==2||copy_arr[i][j]==0) {
                // 아직 바이러스가 확산되지 못한 곳이 있으면 확산 실패
					return;
				}
                // 가장 값이 높은 곳이 바이러스가 마지막에 확신된 시간이다.
				maxtime = Math.max(maxtime, time[i][j]);
			}
		}
		minTime = Math.min(maxtime, minTime);
		
	}


}

* DFS로 경우의 수 찾고, BFS 확산에 사용

* 초기 여러 곳의 값을 저장할 때 리스트 사용

profile
여유를 가지고 Deep Dive

0개의 댓글