BOJ 1600

2020-10-31

위 문제는 백준 사이트의 알고리즘 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을 출력하고 나갑니다.

말이 되고픈 원숭이