0. 개요
구현 문제입니다.
이 문제를 해결하기 위해서 두 가지의 주의 사항이 있을거 같습니다.
1. 골렘의 시작점은 숲 밖이다.
2. 골렘 몸의 일부와 출구를 분리해야 한다.
1. 골렘의 시작점은 숲 밖이다.
아래와 같은 그림일 때, 어떻게 동작될까요?
정답은, 왼쪽으로 회전한다는 것입니다.
왼쪽으로 2회전이 가능하기 때문에 아래와 같은 그림으로 골렘의 이동이 종료됩니다.
이렇게 골렘은 숲 밖에서 부터 회전을 할 수 있습니다.
저는 이를 위해서 숲 바깥, 숲 위, 숲 안쪽을 각 -2, -1, 0 으로 표현했습니다.
이를 그림으로 표현하면 아래와 같습니다.
행과 열의 입력의 범위가 70 이하입니다.
저의 경우 편의상 80*80 배열을 만들어 (3,3) 부터 숲 내부가 되도록 고정시켰습니다.
좌측열에 패딩을 준 이유는
골렘 중앙을 기준으로 하, 좌, 우로 이동이 가능한지 판단하기 때문입니다.
골렘 중앙을 기준으로 두 칸 이내의 범위를 확인해야 하는데,
패딩을 줘야 ArrayOutOfBoundsException을 피할 수 있습니다.
2. 골렘 몸의 일부와 출구를 분리해야 한다.
저는 골렘의 일반 몸의 값을 홀수로 하고, 출구의 값을 짝수로 했습니다.
이렇게 한 이유는
BFS로 정령이 갈 수 있는 최대 행을 찾을 때, 짝수일 경우 다른 골렘으로 이동할 수 있게 하기 위함입니다.
이를 그림으로 표현하면 아래와 같습니다.
[구현 코드]
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 Main {
private static final int EMPTY = 0;
private static final int TOP = -1;
private static final int OUT_OF_RANGE = -2;
// 네 방향 벡터
private static final int[] DX = {-1, 0, 1, 0};
private static final int[] DY = {0, 1, 0, -1};
// 아래 이동 벡터
private static final int[] DOWN_DX = {1, 2, 1};
private static final int[] DOWN_DY = {-1, 0, 1};
// 좌, 우 회전 벡터
private static final int[] ROTATE_DX = {-1, 0, 1, 1, 2}; // 좌, 우 회전의 행 확인 범위는 같음
private static final int[] LEFT_ROTATE_DY = {-1, -2, -1, -2, -1};
private static final int[] RIGHT_ROTATE_DY = {1, 2, 1, 2, 1};
private static final int[][] map = new int[80][80];
private static int r, c, k, count = 1;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer stz = new StringTokenizer(br.readLine());
int answer = 0;
r = Integer.parseInt(stz.nextToken());
c = Integer.parseInt(stz.nextToken());
k = Integer.parseInt(stz.nextToken());
// 1. 마법의 숲 초기화
initMap();
while (k-- > 0) {
stz = new StringTokenizer(br.readLine());
int y = Integer.parseInt(stz.nextToken()) + 2; // 3번 인덱스부터 쓰기 위해 2를 더함(입력은 1부터 주어짐)
int d = Integer.parseInt(stz.nextToken());
Node node = new Node(1, y, d); // 골렘 중앙 값
// 조건에 의한 움직임이 멈출 때 까지 반복
while (true) {
if (canMoveDown(node)) { // 아래로 이동할 수 있는 경우
node.x += 1;
} else if (canMoveLeftOrRight(node, Direction.LEFT)) { // 좌측 회전을 할 수 있는 경우
node.x += 1;
node.y -= 1;
node.d = (node.d == 0 ? DX.length - 1 : node.d - 1);
} else if (canMoveLeftOrRight(node, Direction.RIGHT)) { // 우측 회전을 할 수 있는 경우
node.x += 1;
node.y += 1;
node.d = (node.d + 1) % DX.length;
} else {
// 골렘이 숲 범위를 벗어 나면 초기화
if (node.x < 4 || node.y < 4 || node.x >= r + 3 || node.y >= c + 3) {
initForest();
break;
}
// 중간 좌표를 기준으로 골렘 공간 차지
map[node.x][node.y] = count;
for (int t = 0; t < DX.length; t++) {
int nx = node.x + DX[t];
int ny = node.y + DY[t];
if (isOutOfRange(nx, ny)) continue;
// 출구이면 짝수가 될 수 있도록 1 추가
map[nx][ny] = (t == node.d) ? count + 1 : count;
}
// 항상 출구를 제외한 위치는 홀수, 출구는 짝수 유지
count += 2;
answer += bfs(node);
break;
}
}
}
System.out.print(answer);
br.close();
}
private static int bfs(Node node) {
Queue<Node> qu = new LinkedList<>();
qu.add(node);
boolean[][] visited = new boolean[r + 4][c + 4];
visited[node.x][node.y] = true;
int max = node.x;
while (!qu.isEmpty()) {
Node cn = qu.poll();
for (int t = 0; t < DX.length; t++) {
int nx = cn.x + DX[t];
int ny = cn.y + DY[t];
if (isOutOfRange(nx, ny) || map[nx][ny] <= 0 || visited[nx][ny]) continue;
// 같은 골렘이거나, 골렘의 출구이면 다른 골렘으로 이동 가능
if (map[cn.x][cn.y] == map[nx][ny] ||
map[cn.x][cn.y] + 1 == map[nx][ny] ||
map[cn.x][cn.y] % 2 == 0) {
visited[nx][ny] = true;
qu.add(new Node(nx, ny, -1));
max = Math.max(max, nx); // 정령이 갈 수 있는 최대 위치 저장
}
}
}
// 상단의 길이 만큼 뺌(0번 인덱스 부터 시작하므로 3이 아닌 2를 빼야 함)
return max - 2;
}
private static void initMap() {
// 범위를 벗어나는지 초기화
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
map[i][j] = OUT_OF_RANGE;
}
}
for (int i = 0; i < 3; i++) {
for (int j = 3; j < c + 3; j++) {
map[i][j] = TOP;
}
}
initForest();
}
// (3,3) 부터 유효 공간으로 사용
private static void initForest() {
for (int i = 3; i < r + 3; i++) {
for (int j = 3; j < c + 3; j++) {
map[i][j] = EMPTY;
}
}
}
// 밑으로 움직일 수 있는지 판단
private static boolean canMoveDown(Node node) {
for (int i = 0; i < DOWN_DX.length; i++) {
int nx = node.x + DOWN_DX[i];
int ny = node.y + DOWN_DY[i];
// 범위를 벗어나거나 이전 골렘이 선점하고 있으면 false
if (isOutOfRange(nx, ny) || map[nx][ny] > EMPTY) {
return false;
}
}
return true;
}
private static boolean canMoveLeftOrRight(Node node, Direction direction) {
for (int i = 0; i < ROTATE_DX.length; i++) {
int nx = node.x + ROTATE_DX[i];
int ny = node.y + (direction == Direction.LEFT ? LEFT_ROTATE_DY[i] : RIGHT_ROTATE_DY[i]);
// 범위를 벗어나거나 이전 골렘이 선점하고 있으면 false
if (isOutOfRange(nx, ny) || map[nx][ny] > EMPTY) {
return false;
}
}
return true;
}
private static boolean isOutOfRange(int x, int y) {
return map[x][y] == OUT_OF_RANGE;
}
private enum Direction {
LEFT, RIGHT
}
private static class Node {
int x, y, d;
public Node(int x, int y, int d) {
this.x = x;
this.y = y;
this.d = d;
}
}
}
'알고리즘' 카테고리의 다른 글
소프티어 - 효도 여행 (Java) (0) | 2024.06.26 |
---|---|
소프티어 - 나무 섭지 (Java) (0) | 2024.06.22 |
백준 - 3197 백조의 호수(Java) (0) | 2023.09.30 |
프로그래머스 - 상담원 인원 (Java) (4) | 2023.08.08 |
백준 - 17825 주사위 윷놀이 (Java) (0) | 2023.07.25 |