본문 바로가기

Dot Algo∙ DS/PS

[BOJ] 백준 5427번 불 (Java)

    #5427 불

    난이도 : 골드 4

    유형 : 그래프 탐색 / BFS / 구현

     

    5427번: 불

    상근이는 빈 공간과 벽으로 이루어진 건물에 갇혀있다. 건물의 일부에는 불이 났고, 상근이는 출구를 향해 뛰고 있다. 매 초마다, 불은 동서남북 방향으로 인접한 빈 공간으로 퍼져나간다. 벽에

    www.acmicpc.net

    ▸ 문제

    상근이는 빈 공간과 벽으로 이루어진 건물에 갇혀있다. 건물의 일부에는 불이 났고, 상근이는 출구를 향해 뛰고 있다.

    매 초마다, 불은 동서남북 방향으로 인접한 빈 공간으로 퍼져나간다. 벽에는 불이 붙지 않는다. 상근이는 동서남북 인접한 칸으로 이동할 수 있으며, 1초가 걸린다. 상근이는 벽을 통과할 수 없고, 불이 옮겨진 칸 또는 이제 불이 붙으려는 칸으로 이동할 수 없다. 상근이가 있는 칸에 불이 옮겨옴과 동시에 다른 칸으로 이동할 수 있다.

    빌딩의 지도가 주어졌을 때, 얼마나 빨리 빌딩을 탈출할 수 있는지 구하는 프로그램을 작성하시오.

     입력

    첫째 줄에 테스트 케이스의 개수가 주어진다. 테스트 케이스는 최대 100개이다.

    각 테스트 케이스의 첫째 줄에는 빌딩 지도의 너비와 높이 w와 h가 주어진다. (1 ≤ w,h ≤ 1000)

    다음 h개 줄에는 w개의 문자, 빌딩의 지도가 주어진다.

    • '.': 빈 공간
    • '#': 벽
    • '@': 상근이의 시작 위치
    • '*': 불

    각 지도에 @의 개수는 하나이다.

     출력

    각 테스트 케이스마다 빌딩을 탈출하는데 가장 빠른 시간을 출력한다. 빌딩을 탈출할 수 없는 경우에는 "IMPOSSIBLE"을 출력한다.

     

    문제 풀이  

    처음에 접근한 방식은 불번지는 시간을 map[][]배열에 표기하여 시간별로 통과할 수 있게 작성하였다. 예를들면, 3초에 번진 불은 1~3초안에 지나면 통과할 수 있다. 그러나 25%까지 통과하고 시간초과가 발생했다. 매번 상근이는 똑같은 시작점에서 출발을 하니 최악의 케이스의 경우 통과하지 못한 것 같다. 

     

    실패코드↓

    더보기
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Queue;
    import java.util.StringTokenizer;
    
    public class Main {
    	static int w,h;
    	static int[][] map;
    	static boolean[][] check;
    	static int[] dx = {-1,1,0,0};
    	static int[] dy = {0,0,-1,1};
    	public static void main(String[] args) throws IOException{
    		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    		int tc = Integer.parseInt(br.readLine());
    		
    		StringBuilder sb = new StringBuilder();
    		while(tc-->0) {
    			StringTokenizer st = new StringTokenizer(br.readLine());
    			w = Integer.parseInt(st.nextToken());
    			h = Integer.parseInt(st.nextToken());
    			int sx= 0, sy =0;
    			map = new int[h][w];
    			
    			int space =0;
    			for(int i=0; i<h; i++) {
    				String line = br.readLine();
    				// #: 0, *: 7, @: 29, .: 11
    				for(int j=0; j<w; j++) {
    					int c = line.charAt(j)-'#';
    					if(c==29) {
    						sy = i;
    						sx = j;
    						space++;
    						map[i][j] = -1;
    					}else if(c==7){
    						map[i][j] = 1001;
    					}else if(c==11) {
    						map[i][j] = -1;
    						space++;
    					}else {
    						map[i][j] = c;
    					}
    				}
    			}
    			
    			int time =1;
    			if(sx==0 || sx==w-1 || sy==0 || sy==h-1) {
    				sb.append(1+"\n");
    			}else {
    				List<int[]> person = new ArrayList<>();
    				person.add(new int[] {sx,sy});
    				out : while(true) {
    					// 1. 불 번짐 
    					check = new boolean[h][w];
    					int newFire = 0;
    					for(int i=0; i<h; i++) {
    						for(int j=0; j<w; j++) {
    							if(map[i][j] ==0) continue;
    							if(!check[i][j] && 
    									(map[i][j] == time-1 ||map[i][j] == 1001)) {
    								newFire += fireMove(j,i, time);
    							}
    						}
    					}
    					
    					// 2. 상근 이동 (1번 불번짐 영향 x, 그러나 번질 곳으로 이동은 x)
    					int res=0;
    					for(int[] pos : person) {
    						 res = escape(pos[0], pos[1], time);
    						 if(res== 1) {
    								sb.append((time+1)+"\n");
    								break out;
    							}
    					}
    					if(newFire ==0 || time>w*h) {
    						sb.append("IMPOSSIBLE\n");
    						break;
    					}
    					time++;
    				}
    			}
    		}
    		System.out.println(sb.toString());
    	}
    	
    	static int escape(int x, int y, int time) {
    		Queue<int[]> q = new LinkedList<>();
    		check = new boolean[h][w];
    		q.add(new int[] {x,y,1});
    		check[y][x] = true;
    		
    		while(!q.isEmpty()) {
    			int[] p = q.poll();
    			int px = p[0];
    			int py = p[1];
    			int move = p[2];
    			
    			if(px==0 || px==w-1 || py==0 || py==h-1) {
    				return 1;
    			}
    			if(move> time) return -1;
    			for(int i=0; i<4; i++) {
    				int nx = px + dx[i];
    				int ny = py + dy[i];
    				
    				if(nx<0 || nx>w-1 || ny<0 || ny>h-1) continue;
    				if(check[ny][nx] || map[ny][nx] == 0 || map[ny][nx] == 1001) continue;
    				
    				if(map[ny][nx] ==-1 || map[ny][nx] >= move) {
    					check[ny][nx] = true;
    					q.add(new int[] {nx, ny, move+1});
    				}
    			}
    		}
    		return -1;
    	}
    	
    	static int fireMove(int x, int y, int time) {
    		int cnt=0;
    		check[y][x] = true;
    		int px = x;
    		int py = y;
    		
    		for(int i=0; i<4; i++) {
    			int nx = px+dx[i];
    			int ny = py+dy[i];
    			
    			if(nx<0 || nx>w-1 || ny<0 || ny>h-1) continue;
    			if(check[ny][nx]) continue;
    			
    			// 빈공간 , 상근 위치 
    			if(map[ny][nx] == -1) {
    				check[ny][nx] = true;
    				map[ny][nx] = time;
    				cnt++;
    			}
    		}
    		return cnt;
    	}
    }

     

    그래서 상근이의 위치 상태와 불의 위치 상태를 좀 더 동적으로 접근했다. Queue를 이용하여 매 시간별로 각 상태를 체크해주었다. 위의 방식으로 접근했던 이유는 불과 상근이의 위치가 겹치게 되는 부분 처리가 가능할까라는 생각이 들었기 때문인데, 두 개의 Queue를 생성하여 한 싸이클에 각자의 위치를 저장하고 있으면 현재 상근이의 위치에 불이 번지더라도 Queue에 상근의 위치가 있기 때문에 탐색이 가능했다.

     

    설계

    1. fire, person의 위치를 저장하는 두 개의 Queue를 생성하여 정보를 저장한다. 
    2. 싸이클을 돌린다. 만약 상근이의 Queue가 비게된다면 더이상 움직일 수 없으므로 반복문을 종료한다. if(person.isEmpty()) break;
      1. 첫 번째로, 불 번지기 메서드를 동작한다. fireMarking(px, py);
        1. 현재 불의 상하좌우로 이동한 다음 다시 fire queue에 저장한다. fire.add(new int[] {nx,ny});
      2. 그 다음으로, 상근 이동하기 메서드를 동작한다. escape(px, py, time);
        1. 빈공간'.'으로 상근이를 이동시킨 다음 person queue에 저장한다. person.add(new int[] {nx, ny, time+1});
        2. 만약 그래프 범위를 벗어나게된다면 탈출을 성공한 것이므로 싸이클을 종료한다.

     

    풀이 코드 

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.LinkedList;
    import java.util.Queue;
    import java.util.StringTokenizer;
    
    public class Main {
    	static int w,h;
    	static char[][] map;
    	static Queue<int[]> person;
    	static Queue<int[]> fire;
    	static int[] dx = {-1,1,0,0};
    	static int[] dy = {0,0,-1,1};
    	public static void main(String[] args) throws IOException{
    		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    		int tc = Integer.parseInt(br.readLine());
    		
    		StringBuilder sb = new StringBuilder();
    		while(tc-->0) {
    			StringTokenizer st = new StringTokenizer(br.readLine());
    			w = Integer.parseInt(st.nextToken());
    			h = Integer.parseInt(st.nextToken());
    			
    			map = new char[h][w];
    			fire = new LinkedList<>();
    			person = new LinkedList<>();
    			for(int i=0; i<h; i++) {
    				String line = br.readLine();
    				for(int j=0; j<w; j++) {
    					char c = line.charAt(j);
    					if(c=='@') {
    						person.add(new int[] {j,i,0});
    					}else if(c=='*') {
    						fire.add(new int[] {j,i});
    					}
    					map[i][j] = c;
    				}
    			}
    			int res= -1;
    			out : while(true) {
    				// 1. 불 번짐
    				int fSize = fire.size();
    				for(int i=0; i<fSize; i++) {
    					int[] pos = fire.poll();
    					int px = pos[0], py = pos[1];
    					fireMarking(px, py);
    				}
    				
    				// 2. 상근 이동 (1번 불번짐 영향 x, 그러나 번질 곳으로 이동은 x)
    				int pSize = person.size();
    				for(int i=0; i<pSize; i++) {
    					int[] pos = person.poll();
    					int px = pos[0], py = pos[1], time =pos[2];
    					res = escape(px, py, time);
    					if(res != -1) {
    						break out;
    					}
    				}
    				if(person.isEmpty()) break;
    			}
    			
    			if(res !=-1) sb.append(res+"\n");
    			else sb.append("IMPOSSIBLE\n");
    		}
    		System.out.println(sb.toString());
    	
    	}
    	
    	static int escape(int x, int y, int time) {
    		for(int i=0; i<4; i++) {
    			int nx = x + dx[i];
    			int ny = y + dy[i];
    			
    			if(nx<0 || nx>w-1 || ny<0 || ny>h-1) {
    				return time+1;
    			}
    			
    			if(map[ny][nx] == '.') {
    				map[ny][nx] = '@';
    				person.add(new int[] {nx, ny, time+1});
    			}
    		}
    		return -1;
    		
    	}
    	
    	static void fireMarking(int x, int y) {
    		for(int i=0; i<4; i++) {
    			int nx = x+dx[i];
    			int ny = y+dy[i];
    			
    			if(nx<0 || nx>w-1 || ny<0 || ny>h-1) continue;
    			
    			// 빈공간 , 상근 위치 
    			if(map[ny][nx] == '.' || map[ny][nx] == '@') {
    				map[ny][nx] = '*';
    				fire.add(new int[] {nx,ny});
    			}
    		}
    	}
    }