백준 - 벽 부수고 이동하기(자바)

김한규·2023년 5월 24일
0

알고리즘 실전

목록 보기
16/16

문제 링크

https://www.acmicpc.net/problem/2206

문제 설명

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을 출력한다.

문제 풀이

이 문제는 단순 최단 경로를 구하는 것에서 벽을 한 개까지 부술 수 있다는 조건이 추가된 문제였다.그래서 내가 생각한 부분은 아래와 같았다.
1. 벽을 부순 적이 없으면 벽을 부수고 이동하기 (카운트를 세어서 구현)
2. 한 번 벽을 부순 적이 있는데 벽을 또 만나면 더 이상 갈 수 없다. (반복문 내 continue 처리)

처음엔 단순하게 이렇게 생각하여 이차원 배열의 visited을 이용해 풀었는데 실패하였다. 이는 벽을 부수고 간 경우가 특정 위치에서 먼저 도달하지만 최종적으로 부수지 않고 간 경우가 더 빠를 수 있다는 반례가 존재하였기 때문에 실패할 수 밖에 없다.
그래서 이를 해결하기 위해서는 visited를 3중 배열로 만들어
2가지의 조건으로 나눠준다.
1. 벽을 부수고 탐색하는 경우 (visited[a][b][1])
2. 벽을 부수지 않고 탐색하는 경우 (visited[a][b][0])

요로케 풀이하니 통과하였다

코드

package exercise_coding.backjun.backjun20230524;

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 exam01 {

    static int N, M;
    final static int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; 
    static int[][] map;

    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];

        for (int i = 0; i < N; i++) {
            String num = br.readLine();
            for (int j = 0; j < M; j++) {
                map[i][j] = num.charAt(j) - '0';
            }
        }

        bfs(0, 0);
    }

    private static void bfs(int x, int y) {
        Queue<int[]> queue = new LinkedList<>();
        
        3차원 배열 => 부수고 이동한 것 / 부수지 않고 이동한 것
        int[][][] visited = new int[N][M][2];
        
        queue.add(new int[]{x, y, 0});

        visited[0][0][0] = 1;

        while (!queue.isEmpty()) {
            int[] ints = queue.poll();
            int a = ints[0], b = ints[1], cnt = ints[2];
			
            //해당 목적지에 도착할 수 있다면 종료
            if (a == N - 1 && b == M - 1) {
                System.out.println(visited[a][b][cnt]);
                return;
            }

            for (int[] dir : dirs) {
                int nA = a + dir[0];
                int nB = b + dir[1];

                if (nA < 0 || nA >= N || nB < 0 || nB >= M) {
                    continue;
                }

                if (visited[nA][nB][cnt] != 0) {
                    continue;
                }
				
                //주어진 값에서 1이면서 아직 한번도 벽을 부수지 않았다면..
                if (map[nA][nB] == 1) {
                    if (cnt == 0) {
                        queue.offer(new int[]{nA, nB, cnt + 1});
                        visited[nA][nB][cnt + 1] = visited[a][b][cnt] + 1;
                    }
                } else {
                //벽을 부술 필요가 없으므로 그대로 한다.
                    visited[nA][nB][cnt] = visited[a][b][cnt] + 1;
                    queue.offer(new int[]{nA, nB, cnt});
                }
            }

        }
        System.out.println(-1);
    }

}
profile
사회에 기여하는 개발자가 되기 위해 성장하고 있습니다!

0개의 댓글