문제에서는 격자판에 있는 두 개의 레이저 통신 지점(문자 C
)을 연결하는 경로를 찾되,
경로 상에서 꺾이는 횟수(즉, 거울 사용 횟수)를 최소화하는 것이 목표입니다.
격자에는 벽(*
)이 존재하여 이동이 제한되고, 레이저 빔은 한 방향으로 쭉 진행하다가
거울을 사용해 방향을 바꿀 수 있습니다.
이 문제는 너비 우선 탐색(BFS)를 통해 해결할 수 있습니다.
BFS를 사용하여 각 위치에 도달할 때까지 사용한 거울(방향 전환)의 최소 횟수를 관리하며 탐색합니다.
각 BFS 노드는 다음 정보를 포함합니다:
cnt
moveDir
(상하좌우 0~3로 표현, 시작시에는 방향이 정해지지 않아 -1
사용)한 좌표에 도달하더라도 도착한 방향에 따라 이후 전개될 경로가 달라질 수 있습니다.
따라서, visited[x][y][dir]
형태의 3차원 방문 배열을 사용해
각 위치에 대해 4가지 방향별 최소 거울 사용 횟수를 기록합니다.
초기 코드에서는
if (i == moveDir && visited[dx][dy] > cnt) { ... }
else if (i != moveDir && visited[dx][dy] > cnt + 1) { ... }
와 같이 ‘>’ 연산자로 방문 배열을 검사했습니다.
cnt
또는 cnt + 1
)로 도달하는 경우에도 방향이 다르면 이후 경로에 영향을 줄 수 있으므로,수정된 코드는 다음과 같습니다:
if (i == moveDir && visited[dx][dy] >= cnt) {
visited[dx][dy] = cnt;
queue.add(new int[]{dx, dy, cnt, i});
} else if (i != moveDir && visited[dx][dy] >= cnt + 1) {
visited[dx][dy] = cnt + 1;
queue.add(new int[]{dx, dy, cnt + 1, i});
}
초기에는 이전에 진행한 반대 방향으로 바로 돌아가는 경우를 제거하기 위해 아래 조건을 사용했습니다:
if ((moveDir + 2) % 4 == i) continue;
moveDir
이 -1
이므로, 이 조건에서 계산 오류가 발생할 수 있습니다.수정:
시작이 아닐 때만 역방향 체크를 적용하도록 수정합니다.
if (moveDir != -1 && (moveDir + 2) % 4 == i) continue;
초기에는 2차원 방문 배열을 사용했지만,
같은 좌표에 대해 방향별로 거울 사용 횟수가 달라질 수 있으므로 메모리 초과 문제가 발생했습니다.
해결책:
각 좌표마다 4가지 방향의 정보를 저장하기 위해 3차원 방문 배열을 사용합니다.
int[][][] visited = new int[N][M][4];
입력 처리
C
를 시작점으로, 두 번째 C
는 도착점으로 처리하며, 시작점은 탐색 편의를 위해 '.'
로 변경합니다.BFS 초기화
visited
는 모든 값을 Integer.MAX_VALUE
로 초기화합니다.BFS 수행
(moveDir + 2) % 4 == i
조건으로 차단합니다.출력
import java.io.*;
import java.util.*;
public class Main {
private static int N, M;
private static char[][] map;
private static int sx = -1, sy = -1;
private static int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
public static void main(String[] args) throws IOException {
inputSetting();
System.out.println(getAnswerByBfs());
}
private static int getAnswerByBfs() {
int answer = Integer.MAX_VALUE;
Queue<int[]> queue = new ArrayDeque<>();
int[][][] visited = new int[N][M][4];
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
Arrays.fill(visited[i][j], Integer.MAX_VALUE);
}
}
// 초기 상태: 시작점의 방향은 정해지지 않았으므로 -1 사용
queue.add(new int[]{sx, sy, 0, -1});
while (!queue.isEmpty()) {
int[] cur = queue.poll();
int x = cur[0], y = cur[1], cnt = cur[2], moveDir = cur[3];
// 도착점에 도달한 경우 최소 거울 사용 횟수 갱신
if (map[x][y] == 'C') {
answer = Math.min(answer, cnt);
}
for (int i = 0; i < 4; i++) {
// 시작이 아닌 경우 역방향(바로 뒤로 가는) 이동은 제외
if (moveDir != -1 && (moveDir + 2) % 4 == i) continue;
int dx = x + dir[i][0];
int dy = y + dir[i][1];
if (dx < 0 || dx >= N || dy < 0 || dy >= M) continue;
if (map[dx][dy] == '*') continue;
// 같은 방향이면 거울 추가 없이, 다른 방향이면 거울 1개 추가
if (moveDir == i && visited[dx][dy][i] >= cnt) {
visited[dx][dy][i] = cnt;
queue.add(new int[]{dx, dy, cnt, i});
} else if (moveDir != i && visited[dx][dy][i] >= cnt + 1) {
visited[dx][dy][i] = cnt + 1;
queue.add(new int[]{dx, dy, cnt + 1, i});
}
}
}
return answer - 1;
}
private static void inputSetting() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
M = Integer.parseInt(st.nextToken());
N = Integer.parseInt(st.nextToken());
map = new char[N][M];
for (int i = 0; i < N; i++) {
String inputString = br.readLine();
for (int j = 0; j < M; j++) {
map[i][j] = inputString.charAt(j);
if (sx == -1 && map[i][j] == 'C') {
sx = i;
sy = j;
map[i][j] = '.';
}
}
}
}
}
문제를 해결하는 과정에서 여러 반례를 만나며 다음과 같이 수정했습니다:
방문 조건 수정
visited[dx][dy] > cnt
로만 비교했으나,>=
로 변경하였습니다.역방향 이동 체크 수정
moveDir
이 -1
일 때 (moveDir + 2) % 4
계산에서 오류가 발생하므로,if (moveDir != -1 && (moveDir + 2) % 4 == i) continue;
로 조건문을 추가하였습니다.메모리 초과 문제 해결
이러한 수정 과정을 통해 최종적으로 올바른 풀이를 완성할 수 있었습니다. 🎉