Uknow's Lab.
article thumbnail

https://www.acmicpc.net/problem/1600

 

1600번: 말이 되고픈 원숭이

첫째 줄에 정수 K가 주어진다. 둘째 줄에 격자판의 가로길이 W, 세로길이 H가 주어진다. 그 다음 H줄에 걸쳐 W개의 숫자가 주어지는데, 0은 아무것도 없는 평지, 1은 장애물을 뜻한다. 장애물이 있

www.acmicpc.net

 

 

난이도 : 골드 3
태그 : 그래프 탐색, 그래프 이론, 너비 우선 탐색

 

 

1. 설명

이전에 많이 해보았던 상하좌우 4방향으로 이동하는 BFS 문제에,

체스의 나이트 방향으로 움직일 수 있는 조건이 더해진 문제입니다.

 

단, 나이트 이동의 경우 k번이라는 제약조건이 있으므로,

말 이동을 k번 이하로 사용하면서 최단 경로로 목표지점에 도달하는게 핵심입니다.

 

 

 

2. 소스코드

Data Class - Point, dx & dy

<kotlin />
data class Point(var x: Int, var y: Int, var d: Int, var k: Int)

 

Point는 x, y, 이동한 횟수(d), 말로 이동한 횟수(k)를 담을 data class 입니다.

 

<kotlin />
val dx = arrayOf(0, 0, 1, -1) val dy = arrayOf(1, -1, 0, 0) val hx = arrayOf(-2, -2, 2, 2, 1, -1, 1, -1) val hy = arrayOf(1, -1, 1, -1, 2, 2, -2, -2)

 

dx, dy는 상하좌우 이동을 할때 쓰이며,

hx, hy는 말 이동을 할때 쓰입니다.

 

 

인풋값 세팅

<kotlin />
val br = BufferedReader(InputStreamReader(System.`in`)) val k = br.readLine().toInt() val wh = br.readLine().split(" ") val w = wh[0].toInt() val h = wh[1].toInt() val map = Array(h) { Array(w) { 0 } } repeat(h) { x -> val st = StringTokenizer(br.readLine(), " ") repeat(w) { y -> map[x][y] = st.nextToken().toInt() } }

 

 

흔히 세로길이 - 가로길이 순으로 주어지지만,

본 문제에서는 가로길이(w)가 먼저, 세로길이(h)가 나중에 주어집니다.

 

 

BFS

<code />
val q: Queue<Point> = LinkedList() val visited = Array(h) { Array(w) { Array(k + 1) { false } } } // 맨 왼쪽 위 -> 맨 오른쪽 아래 q.offer(Point(0, 0, 0, 0)) visited[0][0][0] = true while (q.isNotEmpty()) { val target = q.poll() val tx = target.x val ty = target.y val tk = target.k val td = target.d if (tx == h - 1 && ty == w - 1) { println(td) return } // 상하좌우 for (i in 0 until 4) { val nx = tx + dx[i] val ny = ty + dy[i] if (nx !in 0 until h || ny !in 0 until w || visited[nx][ny][tk] || map[nx][ny] == 1) continue q.offer(Point(nx, ny, td + 1, tk)) visited[nx][ny][tk] = true } // knight 처럼 움직임 if (tk < k) { for (i in 0 until 8) { val nx = tx + hx[i] val ny = ty + hy[i] val nk = tk + 1 if (nx !in 0 until h || ny !in 0 until w || visited[nx][ny][nk] || map[nx][ny] == 1) continue q.offer(Point(nx, ny, td + 1, nk)) visited[nx][ny][nk] = true } } }

 

BFS를 수행하는 부분입니다.

 

말 이동을 k번째 했을때의 상태를 저장하기 위해,

visited 배열을 3차원 형태로, h,w, k + 1크기만큼 만듭니다.

 

이후, 일반 이동(상하좌우)와 나이트이동(8방향) 각각에 대해 처리하고,

나이트 이동의 경우 k를 1씩 증가해 처리합니다.

 

단, 나이트 이동은 최대 k번만 할 수 있으므로 tk < k일때만 수행합니다.

 

 

 

전체 소스코드

<kotlin />
import java.io.BufferedReader import java.io.InputStreamReader import java.util.* data class Point(var x: Int, var y: Int, var d: Int, var k: Int) fun main() { val dx = arrayOf(0, 0, 1, -1) val dy = arrayOf(1, -1, 0, 0) val hx = arrayOf(-2, -2, 2, 2, 1, -1, 1, -1) val hy = arrayOf(1, -1, 1, -1, 2, 2, -2, -2) val br = BufferedReader(InputStreamReader(System.`in`)) val k = br.readLine().toInt() val wh = br.readLine().split(" ") val w = wh[0].toInt() val h = wh[1].toInt() val map = Array(h) { Array(w) { 0 } } repeat(h) { x -> val st = StringTokenizer(br.readLine(), " ") repeat(w) { y -> map[x][y] = st.nextToken().toInt() } } val q: Queue<Point> = LinkedList() val visited = Array(h) { Array(w) { Array(k + 1) { false } } } // 맨 왼쪽 위 -> 맨 오른쪽 아래 q.offer(Point(0, 0, 0, 0)) visited[0][0][0] = true while (q.isNotEmpty()) { val target = q.poll() val tx = target.x val ty = target.y val tk = target.k val td = target.d if (tx == h - 1 && ty == w - 1) { println(td) return } // 상하좌우 for (i in 0 until 4) { val nx = tx + dx[i] val ny = ty + dy[i] if (nx !in 0 until h || ny !in 0 until w || visited[nx][ny][tk] || map[nx][ny] == 1) continue q.offer(Point(nx, ny, td + 1, tk)) visited[nx][ny][tk] = true } // knight 처럼 움직임 if (tk < k) { for (i in 0 until 8) { val nx = tx + hx[i] val ny = ty + hy[i] val nk = tk + 1 if (nx !in 0 until h || ny !in 0 until w || visited[nx][ny][nk] || map[nx][ny] == 1) continue q.offer(Point(nx, ny, td + 1, nk)) visited[nx][ny][nk] = true } } } println(-1) }

 

3. 후기

 

다른 문제들과 다르게, 가로길이 세로길이가 반대 순서로 주어져 엉뚱한 곳에서 시간을 끌고,

3차원 visited 없이 2차원 visited 배열과 Point 데이터 클래스 만으로 해결할 수 있다 생각하여

꽤 많은 시간을 들이고, 꽤 많은 '틀렸습니다' 를 받은 끝에 풀은 문제입니다.

 

마침내 나이트 이동이 0번, 1번, 2번, 3번 ... k번 각각 저장해야 함을 깨닫고

3차원 visited를 사용해 우여곡절 끝에 풀었던 문제였네요 ㅎㅎ...

 

profile

Uknow's Lab.

@유노 Uknow

인생은 Byte와 Double 사이 Char다. 아무말이나 해봤습니다.