최단 경로 문제이므로 BFS를 사용하는 것이 표준적인 접근. 하지만 '벽을 한 번까지 부술 수 있다'는 조건 때문에 방문 상태를 단순 2차원 배열로 관리할 수 없다. 벽을 부순 상태와 부수지 않은 상태를 구별하여 경로를 탐색해야 하므로, 3차원 배열 visited[row][col][wall_broken_status]를 도입하여 상태를 관리한다.
(x, y)를 저장하고 visited[x][y]로 방문 여부를 체크한다.(r, c)에 벽을 부수고 도달하는 경로와, 벽을 부수지 않고 도달하는 경로는 이후의 탐색에 다른 영향을 미친다. (전자는 더 이상 벽을 부술 수 없고, 후자는 아직 기회가 있다.)(x, y, broken)을 하나의 상태 단위로 정의한다.visited[x][y][broken]의 3차원으로 확장한다. visited[x][y][0]은 (x,y)까지 벽을 한 번도 부수지 않고 도착했음을 의미하고, visited[x][y][1]은 벽을 한 번 부수고 도착했음을 의미한다.BFS 큐에 (x, y, 거리, 벽 파괴 여부)를 저장하여 탐색을 진행한다.
(0, 0, 1, 0) (위치, 거리, 파괴안함)를 큐에 추가하고 visited[0][0][0]을 방문 처리한다.(x, y, dist, broken)를 꺼낸다.(N-1, M-1)에 도달하면 dist를 반환하고 종료한다.(nx, ny)를 탐색한다.map[nx][ny] == 0): visited[nx][ny][broken]이 아직 방문하지 않은 상태라면, (nx, ny, dist + 1, broken)을 큐에 추가하고 방문 처리한다.map[nx][ny] == 1): 아직 벽을 부순 적이 없고(broken == 0), visited[nx][ny][1]이 아직 방문하지 않은 상태라면, 벽을 부수고 이동하는 새로운 상태 (nx, ny, dist + 1, 1)을 큐에 추가하고 방문 처리한다.//BOJ2206 벽 부수고 이동하기
//[www.acmicpc.net/problem/2206](http://www.acmicpc.net/problem/2206)
package BOJ2206;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;
import java.util.stream.Stream;
public class Main {
static int[] dx = {0, -1, 0, 1};
static int[] dy = {-1, 0, 1, 0};
static int[][] map;
static boolean[][][] visited;
static int N, M;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader([System.in](http://System.in)));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
map = new int[N][M];
visited = new boolean[N][M][2];
for (int i = 0; i < N; i++) {
map[i] = Stream.of(br.readLine().split("")).mapToInt(Integer::parseInt).toArray();
}
System.out.println(bfs());
}
public static int bfs() {
Queue<int[]> queue = new ArrayDeque<>();
queue.offer(new int[]{0, 0, 1, 0});//x, y, depth, walls
visited[0][0][0] = true; //정상 이동
visited[0][0][1] = true; //벽 부수고 이동
while (!queue.isEmpty()) {
int[] cur = queue.poll();
int x = cur[0], y = cur[1];
int dist = cur[2];
int broken = cur[3];
if (x == N - 1 && y == M - 1) {
return dist;
}
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (isValid(nx, ny)) {
// 다음 이동할 곳이 벽이 아닌 경우
if (map[nx][ny] == 0) {
// 해당 상태(broken)로 아직 방문하지 않았다면
if (!visited[nx][ny][broken]) {
visited[nx][ny][broken] = true;
queue.offer(new int[]{nx, ny, dist + 1, broken});
}
}
// 다음 이동할 곳이 벽인 경우
else {
// 벽을 부술 기회가 남아있고, 벽을 부순 상태로 아직 방문하지 않았다면
if (broken == 0 && !visited[nx][ny][1]) {
visited[nx][ny][1] = true;
// 벽을 부쉈으므로 상태를 1로 변경
queue.offer(new int[]{nx, ny, dist + 1, 1});
}
}
}
}
}
return -1;
}
public static boolean isValid(int x, int y) {
return 0 <= x && x < N && 0 <= y && y < M;
}
}