Study/Baekjoon

Baekjoon2206: 벽 부수고 이동하기

devyoseph 2022. 1. 16. 16:40

벽 부수고 이동하기

2 초 192 MB 73475 17894 11114 22.737%

문제

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로로 이동하려 한다. 최단경로는 맵에서 가장 적은 개수의 칸을 지나는 경로를 말하는데, 이때 시작하는 칸과 끝나는 칸도 포함해서 센다.

만약에 이동하는 도중에 한 개의 벽을 부수고 이동하는 것이 좀 더 경로가 짧아진다면, 벽을 한 개 까지 부수고 이동하여도 된다.

한 칸에서 이동할 수 있는 칸은 상하좌우로 인접한 칸이다.

맵이 주어졌을 때, 최단 경로를 구해 내는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 1,000), M(1 ≤ M ≤ 1,000)이 주어진다. 다음 N개의 줄에 M개의 숫자로 맵이 주어진다. (1, 1)과 (N, M)은 항상 0이라고 가정하자.

출력

첫째 줄에 최단 거리를 출력한다. 불가능할 때는 -1을 출력한다.

풀이

벽을 한 번 부술 수 있다는 조건이 존재하기 때문에 일반적인 DFS로 풀이하기에는 어려워보였다.

BFS를 이용해 풀이했고 벽을 부술 수 있다는 정보를 x,y 좌표와 함께 배열 안에 넣어 표기하기로 했다.

문제점

BFS의 단점은 수평적인 탐색이기 때문에 다음과 같은 문제를 생각할 수 있다.

1) 초반에 벽을 부수고 움직이는 경우 뒤에 따라오는 벽을 안부쉈을 때 경로의 기록은 막힌다.

2) 벽을 초반에 부수지 않고 나중에 부수는 경우 최소 경로가 될 수 도 있기 때문에 정확한 답을 구할 수 없다

대안

기록을 두 경우로 나눠 진행한다 → 벽을 부순 이후의 배열 기록 / 벽을 부수지 않은 경우의 배열 기록

주의점

벽을 부순다음 이동할 때와 벽을 부수지 않고 이동할 때 기록이 다르게 진행되기 때문에 조건을 잘 나눌 수 있도록 주의한다.

import java.util.*;
public class Main {
	static boolean[][] arr;
	static int[][] check;
	static int[][] checkCrashed;
	static int M,N;
	static int[] dx = {1,-1,0,0};
	static int[] dy = {0,0,1,-1};
	public static void bfs() {
		Queue<int[]> queue = new LinkedList<int[]>();
		check[0][0]=1; //벽을 부수지 않은 기록 배열 처음 위치에 1 입력
		checkCrashed[0][0]=1; //벽을 부순 기록 배열 처음 위치에 1 입력
        
		queue.add(new int[] {0,0,1}); //세번째 값은 벽을 부술 수 있는 기회
        
		while(!queue.isEmpty()) {
			int[] now = queue.poll();
			int nowX = now[0];
			int nowY = now[1];
			int chance = now[2]; //벽 부수기 가능 횟수

				for(int i=0;i<4;i++) {
				int nextX = nowX + dx[i];
				int nextY = nowY + dy[i];
				if(nextX<0 || nextY<0 || nextX>=N || nextY>=M) {
					continue; //경계값을 벗어나면 무조건 제외
				}
				if(arr[nextX][nextY] && chance==0) {
					continue; //벽 부술 기회=0이고 벽을 만나면 제외
				} else if(!arr[nextX][nextY] && chance==1 && check[nextX][nextY]==0) {
				check[nextX][nextY]=check[nowX][nowY]+1;
				queue.add(new int[]{nextX,nextY,1}); //벽이 아니고, 벽 부수기 기회가 있다면 check에 기록
				} else if(!arr[nextX][nextY] && chance==0 && checkCrashed[nextX][nextY]==0) {
				checkCrashed[nextX][nextY]=checkCrashed[nowX][nowY]+1;
				queue.add(new int[]{nextX,nextY,0}); //벽이 아니고, 벽 부수기 기회가 없다면 checkCrashed에 기록
				} else if(arr[nextX][nextY] && chance==1 && checkCrashed[nextX][nextY]==0 ) {
				checkCrashed[nextX][nextY]=check[nowX][nowY]+1;
				queue.add(new int[]{nextX,nextY,0}); //벽이지만 기회가 있다면 차감하고 Crashed에 기록
				} else {
					continue;
				}
			}
		}
	}
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		N=sc.nextInt();
		M=sc.nextInt();
		arr=new boolean[N][M];
		check=new int[N][M];
		checkCrashed=new int[N][M];
		for(int i=0;i<N;i++) {
			String s = sc.next();
			for(int j=0;j<M;j++) {
				if(s.charAt(j)=='1') { //true가 벽
					arr[i][j]=true;
				}
			}
		}
		bfs();
		int max = Math.max(check[N-1][M-1], checkCrashed[N-1][M-1]);
		int min = Math.min(check[N-1][M-1], checkCrashed[N-1][M-1]);
		if(max==0) { //둘 다 0이면 -1 출력
			System.out.println(-1);
		}else if(min==0) { //둘 줄 하나가 0이면 그 중 큰 값을 출력
			System.out.println(max);
		} else { //둘다 0이 아니면 그 중 작은 값을 출력
			System.out.println(min);
		}
}}

'Study > Baekjoon' 카테고리의 다른 글

Baekjoon11725: 트리의 부모, 숏코딩  (0) 2022.01.18
Baekjoon1707: 이분 그래프  (0) 2022.01.17
Baekjoon1697: 숨바꼭질  (0) 2022.01.15
Baekjoon7562: 나이트의 이동  (0) 2022.01.14
Baekjoon7569: 토마토  (0) 2022.01.14