문제
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을 출력한다.
시간 제한 | 메모리 제한 | 제출 | 정답 | 맞힌 사람 | 정답 비율 |
2 초 | 192 MB | 111468 | 28230 | 17526 | 22.739% |
접근: 해당 문제는 최대 1000 * 1000 의 노드 수를 가질 수 있기 때문에 시간 초과가 나기 쉽다. 처음에는 visit을 나누어 생각하지 않아 틀린 답을 찾았다. 꼼꼼하게 문제 상황을 분석하는 것이 중요하다.
구현: map에서 특정 점에 도착했을 때 이전에 벽을 한번 부순 경우와 아직 부수지 않은 경우(cnt가 남아있는 경우)로 나누어서 visit 처리를 해야한다.
1) 3차원 배열로 벽을 부수기 전, 벽을 부순 후로 나누었다.
boolean visit[][][] = new boolean[2][n+1][m+1];
2) BFS 내부 방문 조건은 다음과 같다.
while (!queue.isEmpty()) {
Info now = queue.poll();
if (now.x == n && now.y == m) {
return now.cost;
}
for (int i = 0; i < 4; i++) {
int xx = now.x + mx[i];
int yy = now.y + my[i];
if (xx > 0 && xx <= n && yy > 0 && yy <= m) {
// 이미 방문한 경우
if(visit[now.cnt][xx][yy]) continue;
// 아직 방문하지 않았고 벽인 경우
if (map[xx][yy] == '1' && now.cnt == 1) {
visit[0][xx][yy] = true;
queue.add(new Info(xx, yy, 0, now.cost + 1));
}
// 벽이 아닌 경우는 cnt값을 유지하며 큐에 추가한다.
if (map[xx][yy] == '0') {
visit[now.cnt][xx][yy] = true;
queue.add(new Info(xx, yy, now.cnt, now.cost + 1));
}
}
}
}
소스코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
public class BOJ2206 {
static int n,m;
static char map[][];
static int mx[] = {0, -1, 0, 1};
static int my[] = {-1, 0, 1, 0};
static int BFS(){
Queue<Info> queue = new LinkedList<>();
boolean visit[][][] = new boolean[2][n+1][m+1];
visit[0][1][1] = true;
visit[1][1][1] = true;
queue.add(new Info(1, 1, 1, 1));
while (!queue.isEmpty()) {
Info now = queue.poll();
if (now.x == n && now.y == m) {
return now.cost;
}
for (int i = 0; i < 4; i++) {
int xx = now.x + mx[i];
int yy = now.y + my[i];
if (xx > 0 && xx <= n && yy > 0 && yy <= m) {
// 이미 방문한 경우
if(visit[now.cnt][xx][yy]) continue;
// 아직 방문하지 않았고 벽인 경우
if (map[xx][yy] == '1' && now.cnt == 1) {
visit[0][xx][yy] = true;
queue.add(new Info(xx, yy, 0, now.cost + 1));
}
// 벽이 아닌 경우는 cnt값을 유지하며 큐에 추가한다.
if (map[xx][yy] == '0') {
visit[now.cnt][xx][yy] = true;
queue.add(new Info(xx, yy, now.cnt, now.cost + 1));
}
}
}
}
return -1;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] s = br.readLine().split(" ");
n = Integer.parseInt(s[0]);
m = Integer.parseInt(s[1]);
map = new char[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
char[] input = br.readLine().toCharArray();
for (int j = 1; j <= m; j++) {
map[i][j] = input[j - 1];
}
}
int result = BFS();
System.out.println(result);
}
static class Info{
int x, y, cost;
// 벽 부순 횟수
int cnt;
public Info(int x, int y, int cnt, int cost) {
this.x = x;
this.y = y;
this.cnt = cnt;
this.cost = cost;
}
}
}
https://www.acmicpc.net/problem/2206
2206번: 벽 부수고 이동하기
N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로
www.acmicpc.net
'PS > 백준' 카테고리의 다른 글
[ BOJ 1761 ] 정점들의 거리 ( JAVA ) (0) | 2023.04.10 |
---|---|
[ BOJ 1987 ] 알파벳 ( JAVA ) (0) | 2023.04.05 |
[ BOJ 1162 ] 도로 포장 ( JAVA ) (0) | 2023.03.30 |
[ BOJ 17404 ] RGB거리 2 ( JAVA ) (0) | 2023.03.29 |
[ BOJ 12919 ] A와 B 2 ( JAVA ) (0) | 2023.03.28 |