BOJ 1600
위 문제는 백준 사이트의 알고리즘 1600 문제에 관한 설명입니다.
문제
동물원에서 막 탈출한 원숭이 한 마리가 세상구경을 하고 있다.
그 녀석은 말(Horse)이 되기를 간절히 원했다.
그래서 그는 말의 움직임을 유심히 살펴보고 그대로 따라 하기로 하였다.
말은 말이다.
말은 격자판에서 체스의 나이트와 같은 이동방식을 가진다.
참고로 말은 장애물을 뛰어넘을 수 있다.
근데 원숭이는 한 가지 착각하고 있는 것이 있다.
말은 저렇게 움직일 수 있지만 원숭이는 능력이 부족해서 총 K번만 위와 같이 움직일 수 있고, 그 외에는 그냥 인접한 칸으로만 움직일 수 있다.
대각선 방향은 인접한 칸에 포함되지 않는다.
이제 원숭이는 머나먼 여행길을 떠난다. 격자판의 맨 왼쪽 위에서 시작해서 맨 오른쪽 아래까지 가야한다.
인접한 네 방향으로 한 번 움직이는 것, 말의 움직임으로 한 번 움직이는 것, 모두 한 번의 동작으로 친다.
격자판이 주어졌을 때, 원숭이가 최소한의 동작으로 시작지점에서 도착지점까지 갈 수 있는 방법을 알아내는 프로그램을 작성하시오.
입력
첫째 줄에 정수 K가 주어진다.
둘째 줄에 격자판의 가로길이 W, 세로길이 H가 주어진다.
그 다음 H줄에 걸쳐 W개의 숫자가 주어지는데, 0은 아무것도 없는 평지, 1은 장애물을 뜻한다.
장애물이 있는 곳으로는 이동할 수 없다.
시작점과 도착점은 항상 평지이다.
W와 H는 1이상 200이하의 자연수이고, K는 0이상 30이하의 정수이다.
출력
첫째 줄에 원숭이의 동작수의 최솟값을 출력한다.
시작점에서 도착점까지 갈 수 없는 경우엔 -1을 출력한다.
package day1031;
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 {
static int K, W, H;
static int map[][];
static int dx[] = { -1, 0, 1, 0 };
static int dy[] = { 0, 1, 0, -1 };
static int dx2[] = { -2, -2, -1, 1, 2, 2, 1, -1 };
static int dy2[] = { -1, 1, 2, 2, 1, -1, -2, -2 };
static Queue<Point> queue = new LinkedList<Point>();
static boolean visited[][][];
public static void main(String[] args) throws 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());
map= new int[H][W];
visited = new boolean[K+1][H][W];
for(int i = 0; i < H; i++) {
st = new StringTokenizer(br.readLine());
for(int j = 0 ; j < W; j++) {
map[i][j] = Integer.parseInt(st.nextToken());
}
}
bfs();
}
public static void bfs() {
queue.add(new Point(0,0,0,0));
while(!queue.isEmpty()) {
Point temp = queue.poll();
if(temp.x == H-1 && temp.y == W-1) {
System.out.println(temp.count);
return;
}
for(int dir = 0 ; dir < 4; dir++) {
int nextX = temp.x + dx[dir];
int nextY = temp.y + dy[dir];
if(isIn(nextX,nextY) && !visited[temp.horse][nextX][nextY]&&map[nextX][nextY]!=1) {
visited[temp.horse][nextX][nextY] = true;
queue.add(new Point(nextX,nextY,temp.count+1,temp.horse));
}
}
if(temp.horse+1 <= K) {
for(int dir = 0 ; dir < 8; dir++) {
int nextX = temp.x+dx2[dir];
int nextY = temp.y+dy2[dir];
if(isIn(nextX,nextY)&& !visited[temp.horse+1][nextX][nextY] && map[nextX][nextY]!=1) {
visited[temp.horse+1][nextX][nextY] = true;
queue.add(new Point(nextX,nextY,temp.count+1,temp.horse+1));
}
}
}
}
System.out.println(-1);
}
public static boolean isIn(int nextX, int nextY) {
if(0<=nextX && 0 <= nextY && nextX < H && nextY < W) {
return true;
}
else return false;
}
static class Point {
int x;
int y;
int count;
int horse;
Point(int x, int y, int count, int horse) {
this.x = x;
this.y = y;
this.count = count;
this.horse = horse;
}
}
}
이번 문제는 BFS를 사용했습니다.
static int K, W, H;
static int map[][];
static int dx[] = { -1, 0, 1, 0 };
static int dy[] = { 0, 1, 0, -1 };
static int dx2[] = { -2, -2, -1, 1, 2, 2, 1, -1 };
static int dy2[] = { -1, 1, 2, 2, 1, -1, -2, -2 };
//입력부 입니다.
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());
map= new int[H][W];
visited = new boolean[K+1][H][W];
for(int i = 0; i < H; i++) {
st = new StringTokenizer(br.readLine());
for(int j = 0 ; j < W; j++) {
map[i][j] = Integer.parseInt(st.nextToken());
}
}
특별한 부분은 없습니다.
dx[]
와 dy[]
는 상하좌우를 확인하는 원숭이의 동작을 합니다.
dx2[]
와 dy[]2
는 말이 움직이는 행동을 동작하게 합니다.
public static void bfs() {
queue.add(new Point(0,0,0,0));
while(!queue.isEmpty()) {
Point temp = queue.poll();
if(temp.x == H-1 && temp.y == W-1) {
System.out.println(temp.count);
return;
}
for(int dir = 0 ; dir < 4; dir++) {
int nextX = temp.x + dx[dir];
int nextY = temp.y + dy[dir];
if(isIn(nextX,nextY) && !visited[temp.horse][nextX][nextY]&&map[nextX][nextY]!=1) {
visited[temp.horse][nextX][nextY] = true;
queue.add(new Point(nextX,nextY,temp.count+1,temp.horse));
}
}
if(temp.horse+1 <= K) {
for(int dir = 0 ; dir < 8; dir++) {
int nextX = temp.x+dx2[dir];
int nextY = temp.y+dy2[dir];
if(isIn(nextX,nextY)&& !visited[temp.horse+1][nextX][nextY] && map[nextX][nextY]!=1) {
visited[temp.horse+1][nextX][nextY] = true;
queue.add(new Point(nextX,nextY,temp.count+1,temp.horse+1));
}
}
}
}
System.out.println(-1);
}
BFS 동작부 입니다.
앞부분은 일반 BFS동작과 동일합니다.
queue
에 시작위치를 넣어주고, queue
가 빌때까지 동작합니다.
들어갔던 queue
에서 poll을 해내 하나를 꺼내왔는데,
만약 위치가 도착지점의 좌표와 동일하다면 해당 번째까지 몇번만에 왔는가를 출력합니다.
static class Point {
int x;
int y;
int count;
int horse;
Point(int x, int y, int count, int horse) {
this.x = x;
this.y = y;
this.count = count;
this.horse = horse;
}
}
참고로 Point클래스는 위와 같습니다.
count
는 몇번만에 움직였는가, horse
는 말의 움직임을 몇번했는가를 나타냅니다.
for(int dir = 0 ; dir < 4; dir++) {
int nextX = temp.x + dx[dir];
int nextY = temp.y + dy[dir];
if(isIn(nextX,nextY) && !visited[temp.horse][nextX][nextY]&&map[nextX][nextY]!=1) {
visited[temp.horse][nextX][nextY] = true;
queue.add(new Point(nextX,nextY,temp.count+1,temp.horse));
}
}
이제 원숭이의 움직임 부입니다.
만약 상하좌우를 다 확인하면서 범위 내이며, 방문한적도 없고, 장애물도 없다면 원숭이는 이동할 수 있습니다.
따라서 방문처리를 하고, queue
에 넣어줍시다.
if(temp.horse+1 <= K) {
for(int dir = 0 ; dir < 8; dir++) {
int nextX = temp.x+dx2[dir];
int nextY = temp.y+dy2[dir];
if(isIn(nextX,nextY)&& !visited[temp.horse+1][nextX][nextY] && map[nextX][nextY]!=1) {
visited[temp.horse+1][nextX][nextY] = true;
queue.add(new Point(nextX,nextY,temp.count+1,temp.horse+1));
}
}
말의 움직임 부입니다.
원숭이의 동작들을 queue
에 다 집어넣은 상태이고,
만약 말의 움직임을 할 수 있는 횟수가 남아있다면
8방향을 확인하면서 말의 움직임을 할 수 있는가를 판단합니다.
원숭이의 동작과 구별되는 점은 queue
에 넣을 때 말의 움직임을 한번 사용했으니 horse+1
을 해서 넣어줍니다.
만약 while문의 조건처럼 queue
에 아무것도 없다면?
종료를 위해 -1
을 출력하고 나갑니다.