본문 바로가기

Dot Algo∙ DS/PS

[BOJ] 백준 14502번 연구소 (Java)

    #14502 연구소

    난이도 : 골드 5

    유형 : 그래프 탐색/ 브루트포스/ BFS

     

    14502번: 연구소

    인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다. 연구소는 크

    www.acmicpc.net

    ▸ 문제

     입력

    첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. (3 ≤ N, M ≤ 8)

    둘째 줄부터 N개의 줄에 지도의 모양이 주어진다. 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 위치이다. 2의 개수는 2보다 크거나 같고, 10보다 작거나 같은 자연수이다.

    빈 칸의 개수는 3개 이상이다.

     출력

    첫째 줄에 얻을 수 있는 안전 영역의 최대 크기를 출력한다.

     

     

     

    문제 풀이 

    주어진 맵에 벽 3개를 브루트포스에서 모든 경우로 다 세워본 다음에 바이러스를 퍼트려보고 거기서 가장 안전 영역이 최대인 값을 출력해주면 된다. BFS와 backtracking에 대한 개념이 있다면 차근차근 설계하면 된다.

     

    📚 조건

    • 바이러스는 상하좌우 인접한 빈 칸으로 모두 퍼져나간다.
    • 새로 세울 수 있는 벽은 3개 (꼭 3개 세워야 함)

     

    구상

     1. 벽 3개를 가능한 모든 조합으로 벽을 세운다.

     2. 1번에서 만든 모든 맵을 깊은 복사하여 BFS탐색을 통하여 바이러스를 퍼트린다.

        > 깊은 복사 : 복사된 맵을 값을 변경해도 원본은 변경안됨.

     3. 각 2번의 결과를 카운트하여 안전영역(빈칸)의 갯수를 출력한다.

     

    스케치

    로직 1. 벽 3개를 모든 경우의 수로 세우기

    Map에 백트래킹을 사용하여 벽 3개를 모든 경우의 수로 세워본다.  재귀 stack방식을 사용하여 벽이 3개가 세워졌으면 특정 연산을 실행(로직2)한 다음 제일 마지막에 들어간 부분을 리턴하고 다른 경우의 수의 맵을 또 만드는 방식이다.

    // Backtracking : 벽3개 세우기 
    static void makeWall(int start, int wallNum) {
    	if(wallNum == 3) {
    		return;
    	}
    	for(int i=start; i< n*m; i++) {
    		int x = i/m;
    		int y = i%m;
    		
    		if(map[x][y] ==0) {
    			map[x][y] =1;
    			makeWall(i+1, wallNum+1);
    			map[x][y] =0;
    		}
    	}
    }

     

    로직 2.  깊은 복사한 맵에 바이러스 퍼트리기 BFS

    1번에서 만든 벽 3개를 세운 맵을 깊은 복사하여(원본 값 변경 x) 바이러스를 퍼트린다. 해당 문제는 바이러스의 감염 속도를 조사하는 것은 아니기 때문에 따로 싸이클을 동시에 돌리거나 퍼지는 시간을 카운트 해주지 않아도 된다.

    // Backtracking : 벽3개 세우기 
    static void makeWall(int start, int wallNum) {
    
    	if(wallNum == 3) {
    		// map 복사
    		for (int i = 0; i < n; i++) {
    			for (int j = 0; j < m; j++) {
    				testMap[i][j] = map[i][j];
    			}
    		}
    		
    		for(Pos pos : virus) {
    			bfs(pos.x, pos.y);
    		}
    		
    		max = Math.max(max, getSafeSize());
    		return;
    	}
    	
    	for(int i=start; i< n*m; i++) {
    		int x = i/m;
    		int y = i%m;
    		
    		if(map[x][y] ==0) {
    			map[x][y] =1;
    			makeWall(i+1, wallNum+1);
    			map[x][y] =0;
    		}
    	}
    }
    
    // bfs: virus 퍼트리기
    static void bfs(int x, int y) {
    	for(int i=0; i<4; i++) {
    		int nx = x + dx[i];
    		int ny = y + dy[i];
    		
    		if(nx <0 || ny <0 || nx>n-1 || ny>m-1) continue;
    		
    		if(testMap[nx][ny] ==0) {
    			testMap[nx][ny] =2;
    			bfs(nx,ny);
    		}
    	}
    }
    
    

     

    로직 3. 안전 영역 크기 구하기

    마지막으로 바이러스가 모두 퍼진 각 testMap의 안전영역의 크기를 조사하여 최댓값을 출력해주면 된다.

    static int getSafeSize() {
    	int size =0;
    	for(int i=0; i<n; i++) {
    		for(int j=0; j<m; j++) {
    			if(testMap[i][j] ==0) {
    				size++;
    			}
    		}
    	}
    	return size;
    }

     

    풀이 코드 

    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.StringTokenizer;
    
    public class Main {
    	
    	static int n,m;
    	static int[][] map;
    	static int[][] testMap;
    	static List<Pos> virus;
    	static int[] dx = {1, -1, 0, 0};
    	static int[] dy = {0, 0, 1, -1};
    	static int max =-1;
    	
    	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());
    		
    		map = new int[n][m];
    		testMap = new int[n][m];
    		virus = new ArrayList<>();
    		
    		for(int i=0; i<n; i++) {
    			st = new StringTokenizer(br.readLine());
    			for(int j=0; j<m; j++) {
    				int num = Integer.parseInt(st.nextToken());
    				map[i][j] = num;
    				if(num ==2) {
    					virus.add(new Pos(i,j));
    				}
    			}
    		}
    		
    		makeWall(0,0);
    		System.out.println(max);
    		
    	}
    	
    	// backtracking: 벽3개 세우기 
    	static void makeWall(int start, int wallNum) {
    	
    		if(wallNum == 3) {
    			// map 복사
    			for (int i = 0; i < n; i++) {
    				for (int j = 0; j < m; j++) {
    					testMap[i][j] = map[i][j];
    				}
    			}
    			
    			for(Pos pos : virus) {
    				bfs(pos.x, pos.y);
    			}
    			
    			max = Math.max(max, getSafeSize());
    			return;
    		}
    		
    		// 숫자를 0 ~ n*m 까지 증가시킬때 (i/m, i%m) 을 좌표로 하면 2차원 배열의 모든 인덱스를 탐색
    		for(int i=start; i< n*m; i++) {
    			int x = i/m;
    			int y = i%m;
    			
    			if(map[x][y] ==0) {
    				map[x][y] =1;
    				makeWall(i+1, wallNum+1);
    				map[x][y] =0;
    			}
    		}
    	}
    	
    	
    	// bfs: virus 퍼트리기
    	static void bfs(int x, int y) {
    		for(int i=0; i<4; i++) {
    			int nx = x + dx[i];
    			int ny = y + dy[i];
    			
    			if (0 <= nx && nx < n && 0 <= ny && ny < m) {
    				if(testMap[nx][ny] ==0) {
    					testMap[nx][ny] =2;
    					bfs(nx,ny);
    				}
    			}
    		}
    	}
    	
    	static int getSafeSize() {
    		int size =0;
    		for(int i=0; i<n; i++) {
    			for(int j=0; j<m; j++) {
    				if(testMap[i][j] ==0) {
    					size++;
    				}
    			}
    		}
    		return size;
    	}
    }
    class Pos {
    	int x;
    	int y;
    	public Pos(int x, int y) {
    		this.x =x;
    		this.y =y;
    	}
    }