Algorithm/백준(BOJ)

[백준] 17142. 연구소 3 (Java)

Carroti 2022. 9. 19. 13:54

 

🔒 문제

 

17142번: 연구소 3

인체에 치명적인 바이러스를 연구하던 연구소에 승원이가 침입했고, 바이러스를 유출하려고 한다. 바이러스는 활성 상태와 비활성 상태가 있다. 가장 처음에 모든 바이러스는 비활성 상태이고

www.acmicpc.net

 

🔑 알고리즘

브루트포스, 조합론, 그래프 탐색, 너비우선탐색(BFS)

 

 

💡  풀이

전체 바이러스 중 M개의 바이러스를 선택하여 활성 바이러스로 표시한 후,

너비우선탐색(BFS)를 이용하여 모든 빈 칸에 바이러스를 퍼뜨리는 최소 시간을 구했다.

 

입력을 받으며 각 바이러스들의 좌표를 리스트에 저장하고, 퍼뜨려야할 빈 칸의 수를 센다.

 

다음으로 전체 바이러스 중 M개의 바이러스를 고르는데, 이때 바이러스의 순서는 중요하지 않기 때문에 조합을 사용한다.

for (int i = start; i < virusList.size(); i++) {
	if (!selected[i]) {
		selected[i] = true;
		comb(count + 1, i + 1);
		selected[i] = false;
	}
}

 

M개의 바이러스를 골랐다면

활성 바이러스와 비활성 바이러스를 구분하기 위해 고른 바이러스의 값을 2에서 3으로 변경하고

해당 바이러스들의 좌표와 현재 시간에 해당하는 0을 큐에 삽입한다.

for (int i = 0; i < selected.length; i++) {
	if (selected[i]) {
		Virus virus = virusList.get(i);
		queue.add(new Virus(virus.y, virus.x, 0));
		board[virus.y][virus.x] = 3;
	}
}

 

이제 상하좌우 네 방향으로 탐색하며 BFS를 진행한다.

만약 탐색한 곳이 비활성 바이러스(2)인 경우

활성 바이러스에 해당하는 3으로 변경한 후 큐에 삽입하고

 

탐색한 곳이 빈 칸(0)이라면

활성 바이러스에 해당하는 3으로 변경한 후 큐에 삽입하고 바이러스를 퍼뜨린 칸을 세는 count 값을 1 증가시킨다.

이때 증가시킨 값이 처음에 셌던 빈 칸의 값과 동일하다면 모든 빈 칸에 바이러스를 퍼뜨린 것으로 최소 시간을 구한 것이다.

 

탐색한 곳이 벽 혹은 활성 바이러스(1, 3) 이라면 아무것도 하지 않아도 된다.

// 비활성 바이러스
if (board[ny][nx] == 2) {
	queue.add(new Virus(ny, nx, cur.t + 1));
	board[ny][nx] = 3;
}

// 빈칸
if (board[ny][nx] == 0) {
	queue.add(new Virus(ny, nx, cur.t + 1));
	board[ny][nx] = 3;

	if (++pollutionCount == blankCount) {
		min = Math.min(min, cur.t + 1);
		break label1;
	}
}

 

BFS를 진행하면서 현재 시간이 기록된 최소 시간을 넘었다면 유망하지 않기 때문에 더 이상 탐색하지 않는 백트래킹을 사용하여 시간을 단축시킬 수 있다.

if (cur.t > min)
	break;

 

 

* board 의 값에 바이러스를 기록하기 때문에 BFS 시작 전의 board 배열을 복사해두었다가 BFS가 끝나면 원상 복구해야한다.

 

 

✏️ 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	static int[] dy = { -1, 1, 0, 0 };
	static int[] dx = { 0, 0, -1, 1 };
	static int N, M, board[][], min, blankCount;
	static ArrayList<Virus> virusList;
	static boolean[] selected;

	static public 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());
		init();

		for (int i = 0; i < N; i++) {
			st = new StringTokenizer(br.readLine(), " ");
			for (int j = 0; j < N; j++) {
				int num = Integer.parseInt(st.nextToken());
				board[i][j] = num;

				// 빈 칸이라면 count 증가
				if (num == 0)
					blankCount++;
				
				// 바이러스라면 좌표 저장
				if (num == 2) {
					virusList.add(new Virus(i, j, 0));
				}
			}
		}
		selected = new boolean[virusList.size()];
		
		if (blankCount == 0) {
			System.out.println(0);
			return;
		}
		

		comb(0, 0);

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

	private static void init() {
		board = new int[N][N];
		virusList = new ArrayList<>();
		min = Integer.MAX_VALUE;
		blankCount = 0;
	}

	private static void comb(int count, int start) {

		if (count == M) {
			// 임시 배열 생성
			int[][] tmp = new int[N][N];
			for (int i = 0; i < N; i++)
				for (int j = 0; j < N; j++)
					tmp[i][j] = board[i][j];

			// 바이러스 선택 및 표시
			Queue<Virus> queue = new LinkedList<>();
			for (int i = 0; i < selected.length; i++) {
				if (selected[i]) {
					Virus virus = virusList.get(i);
					queue.add(new Virus(virus.y, virus.x, 0));
					board[virus.y][virus.x] = 3;
				}
			}

			int pollutionCount = 0;
			label1: while (!queue.isEmpty()) {
				Virus cur = queue.poll();

				if (cur.t > min)
					break;

				for (int i = 0; i < dy.length; i++) {
					int ny = cur.y + dy[i];
					int nx = cur.x + dx[i];

					if (ny < 0 || ny >= N || nx < 0 || nx >= N)
						continue;

					// 비활성 바이러스
					if (board[ny][nx] == 2) {
						queue.add(new Virus(ny, nx, cur.t + 1));
						board[ny][nx] = 3;
					}

					// 빈칸
					if (board[ny][nx] == 0) {
						queue.add(new Virus(ny, nx, cur.t + 1));
						board[ny][nx] = 3;

						if (++pollutionCount == blankCount) {
							min = Math.min(min, cur.t + 1);
							break label1;
						}
					}
				}
			}

			// 원상 복구
			for (int i = 0; i < N; i++)
				for (int j = 0; j < N; j++)
					board[i][j] = tmp[i][j];

			return;
		}

		for (int i = start; i < virusList.size(); i++) {
			if (!selected[i]) {
				selected[i] = true;
				comb(count + 1, i + 1);
				selected[i] = false;
			}
		}
	}

	static class Virus {
		int y, x, t;

		public Virus(int y, int x, int t) {
			this.y = y;
			this.x = x;
			this.t = t;
		}

	}
}