문제 풀이 날짜: 2023.08.30
포스트 작성일: 2023.08.30
* 학습 목적으로 작성하는 글입니다. 풀이가 정석적이지 못할 수도 있습니다.
문제 출처
키워드
BFS, 동적 계획법
풀이 접근법
- 말 처럼 움직이는 횟수는 총 K회로 제한된다. 그런데 먼저 K회를 모두 말 처럼 움직여버리면 다음의 케이스에서 더 이상 나아갈 수 없는 경우가 생긴다.
2 10 2 0 0 1 0 0 1 0 0 1 0 0 0 1 1 0 0 0 0 1 0
- 즉, 말처럼 움직인 경우와 원숭이가 그냥 이동한 경우를 구분하지 못하면 반례가 생긴다. 그렇기 때문에 각 탐색 때마다 말처럼 움직인 경우, 그냥 이동한 경우를 나누어 탐색한다.
- 즉, 말처럼 0-k번 움직인 경우의 수를 모두 별도의 배열로 관리한다. 마지막에는 이렇게 0부터 k번까지 말처럼 움직인 경우의 수 중에서 가장 최소인 값을 가져온다.
- DP 배열은 현재의 위치(r, c)와 말 처럼 움직인 횟수(k)의 정보를 동시에 가져야 하므로 3차원 배열로 선언한다.
- 메모리 초과가 쉽게 발생할 수 있으므로 위처럼 이미 구했던 점을 queue에 넣지 않도록 주의한다.
- visited 배열 또한 DP 배열과 같은 원리로 선언하여, 원숭이가 k번째로 말처럼 움직였을 때의 DP값을 이미 구한 경우를 스킵시키는 용도로 사용한다.
코드
import java.io.*;
import java.util.*;
public class Main {
private static int K;
private static int W;
private static int H;
private static int[] hdr = { 2, 2, -2, -2, 1, -1, 1, -1 };
private static int[] hdc = { 1, -1, 1, -1, 2, 2, -2, -2 };
private static int[] mdr = { 0, 1, 0, -1 };
private static int[] mdc = { 1, 0, -1, 0 };
private static int[][] board;
private static int[][][] DP;
private static boolean[][][] visited;
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
K = Integer.parseInt(br.readLine());
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
W = Integer.parseInt(st.nextToken());
H = Integer.parseInt(st.nextToken());
board = new int[H][W];
DP = new int[H][W][K + 1]; //(r,c)에 다다를 때까지, K번만큼 말 처럼 움직인 결과를 기록
visited = new boolean[H][W][K + 1];
for (int i = 0; i < H; i++) {
st = new StringTokenizer(br.readLine(), " ");
for (int j = 0; j < W; j++) {
board[i][j] = Integer.parseInt(st.nextToken());
for(int k = 0; k <= K; k++) {
DP[i][j][k] = Integer.MAX_VALUE;
}
}
}
BFS(0, 0);
int result = Integer.MAX_VALUE;
for(int k = 0; k <= K; k++) { //k를 쓴 횟수마다 최소값을 따야 함
result = Math.min(result, DP[H - 1][W - 1][k]);
}
if (W - 1 == 0 && H - 1 == 0) {
result = 0;
}
else if (result == Integer.MAX_VALUE) {
result = -1;
}
System.out.println(result);
}
private static void BFS(int startR, int startC) {
Queue<Node> queue = new ArrayDeque<>();
visited[startC][startR][0] = true;
queue.add(new Node(startC, startR, 0));
while (!queue.isEmpty()) {
Node n = queue.poll();
int nr = 0;
int nc = 0;
if (n.k < K) {
// 1.말처럼 움직여보기 ==> K번만 움직일 수 있다.
for (int i = 0; i < hdr.length; i++) {
nr = n.row + hdr[i];
nc = n.col + hdc[i];
if (nr == 0 && nc == 0) {
continue;
}
if (!isValid(nr, nc, n.k + 1)) { //k + 1번째 이동을 이미 처리했다면 continue
continue;
}
// DP 갱신을 위해 방문체크 하지 않음
if (DP[nr][nc][n.k + 1] >= DP[n.row][n.col][n.k] + 1) { // 이동횟수가 작거나 같다면
DP[nr][nc][n.k + 1] = (DP[n.row][n.col][n.k] == Integer.MAX_VALUE)
? 1 : DP[n.row][n.col][n.k] + 1;
if (nr == H - 1 && nc == W - 1) { return; }
visited[nr][nc][n.k + 1] = true;
queue.add(new Node(nr, nc, n.k + 1));
}
}
}
// 2.상하좌우만 이동 가능
for (int i = 0; i < mdr.length; i++) {
nr = n.row + mdr[i];
nc = n.col + mdc[i];
if (nr == 0 && nc == 0) {
continue;
}
if (!isValid(nr, nc, n.k)) {
continue;
}
// DP 갱신을 위해 방문체크 하지 않음
if (DP[nr][nc][n.k] >= DP[n.row][n.col][n.k] + 1) { // 이동횟수가 작거나 같다면
DP[nr][nc][n.k] = (DP[n.row][n.col][n.k] == Integer.MAX_VALUE)
? 1 : DP[n.row][n.col][n.k] + 1;
if (nr == H - 1 && nc == W - 1) { return; }
visited[nr][nc][n.k] = true;
queue.add(new Node(nr, nc, n.k)); // count 변수 유지
}
}
}
return;
}
private static boolean isValid(int nr, int nc, int k) {
return (nr > -1 && nc > -1 && nr < H && nc < W && !visited[nr][nc][k] && board[nr][nc] != 1);
}
}
class Node {
int row;
int col;
int k; // 말처럼 움직인 횟수
public Node(int row, int col, int k) {
this.row = row;
this.col = col;
this.k = k;
}
}
git 링크
'Study > Problem Solving' 카테고리의 다른 글
[SWEA / Java] 1767번: 프로세서 연결하기 (0) | 2023.08.30 |
---|---|
[백준 / Java] 17070번: 파이프 옮기기 1 (골드5) (0) | 2023.08.30 |
[백준 / Java] 1010번: 다리 놓기 (실버5) (0) | 2023.08.30 |
[백준 / Java] 15961번: 회전초밥 (골드4) (0) | 2023.08.26 |
[SWEA / Java] 1251번: 하나로 (D4) (0) | 2023.08.26 |