잡다한 IT 지식

백준 - 구슬 탈출 2(Kotlin) 본문

알고리즘 문제 풀이

백준 - 구슬 탈출 2(Kotlin)

가나무마 2023. 12. 28. 03:59

제한사항

  • N, M (3 ≤ N, M ≤ 10)

문제 정리

구슬을 사방향 직선으로 움직일 수 있을 때, 빨간 구슬만 나오는 최소 움직임은?

접근 방법

일단, 최단 거리로 풀어야 하므로 BFS가 필요하다.

BFS 사용하면 맵을 최신화 하는 방법은 불가능하다.

따라서, 맵을 최신화하지 않고 구슬을 움직여야 한다.

이 때, 문제점이 발생한다. 맵을 최신화하지 않는다면 구슬의 이동을 계산할 수 없어진다.

따라서, 이동하는 방향을 정항 때, 한 구슬은 뒤로 이동하면 된다.

이동하는 방향에 빨간 구슬이 있으면, 두 구슬을 이동한 후에 파란 구슬은 뒤로 한 칸 이동한다.

반대로 이동하는 방향에 파란 구슬이 있으면, 두 구슬을 이동한 후에 빨간 구슬은 뒤로 한 칸 이동한다.

코드

import java.io.BufferedReader
import java.io.InputStreamReader
import java.util.LinkedList

val directions = listOf(-1 to 0, 0 to 1, 1 to 0, 0 to -1)

fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
    val (n, m) = readLine().split(" ").map { it.toInt() }
    val map = Array(n) { readLine().toCharArray() }
    val redBallLocation = getCharLocation('R', map)
    val blueBallLocation = getCharLocation('B', map)
    val minLeanCount = bfs(redBallLocation, blueBallLocation, map)

    println(minLeanCount)
}

fun getCharLocation(character: Char, map: Array<CharArray>): Pair<Int, Int> {
    for (r in map.indices) {
        for (c in map[0].indices) {
            if (map[r][c] == character) {
                return r to c
            }
        }
    }
    return -1 to -1
}

fun bfs(redBallLocation: Pair<Int, Int>, blueBallLocation: Pair<Int, Int>, map: Array<CharArray>): Int {
    val queue = LinkedList<Pair<Int, Int>>()
    queue.add(redBallLocation)
    queue.add(blueBallLocation)
    val visited = Array(map.size) { Array(map[0].size) { Array(map.size) { BooleanArray(map[0].size) { false } } } }

    var count = 0

    while (queue.isNotEmpty()) {
        if (count >= 10) {
            return -1
        }

        val queueSize = queue.size
        for (i in 0 until (queueSize / 2)) {
            val currentRedBallLocation = queue.poll()
            val currentBlueBallLocation = queue.poll()
            if (visited[currentRedBallLocation.first][currentRedBallLocation.second][currentBlueBallLocation.first][currentBlueBallLocation.second]) {
                continue
            }
            visited[currentRedBallLocation.first][currentRedBallLocation.second][currentBlueBallLocation.first][currentBlueBallLocation.second] = true

            for (direction in directions) {
                var nextRedBallLocation: Pair<Int, Int>
                var nextBlueBallLocation: Pair<Int, Int>

                if (isRedBallFirst(currentRedBallLocation, currentBlueBallLocation, map, direction)) {
                    nextBlueBallLocation = moveDirection(currentBlueBallLocation, direction, map)
                    nextRedBallLocation = moveDirection(currentRedBallLocation, direction, map)
                    
                    if (nextRedBallLocation == nextBlueBallLocation) {
                        if (map[nextBlueBallLocation.first][nextBlueBallLocation.second] == 'O') {
                            continue
                        }
                        nextBlueBallLocation = nextBlueBallLocation.first - direction.first to nextBlueBallLocation.second - direction.second
                    }
                } else {
                    nextBlueBallLocation = moveDirection(currentBlueBallLocation, direction, map)
                    nextRedBallLocation = moveDirection(currentRedBallLocation, direction, map)

                    if (nextRedBallLocation == nextBlueBallLocation) {
                        if (map[nextBlueBallLocation.first][nextBlueBallLocation.second] == 'O') {
                            continue
                        }
                        nextRedBallLocation = nextRedBallLocation.first - direction.first to nextRedBallLocation.second - direction.second
                    }
                }

                if (visited[nextRedBallLocation.first][nextRedBallLocation.second][nextBlueBallLocation.first][nextBlueBallLocation.second]) {
                    continue
                }

                if (map[nextBlueBallLocation.first][nextBlueBallLocation.second] == 'O' || currentRedBallLocation == nextRedBallLocation && currentBlueBallLocation == nextBlueBallLocation) {
                    continue
                } else if (map[nextRedBallLocation.first][nextRedBallLocation.second] == 'O') {
                    return count + 1
                }

                queue.offer(nextRedBallLocation)
                queue.offer(nextBlueBallLocation)
            }
        }
        count++
    }

    return -1
}

fun isRedBallFirst(redBallLocation: Pair<Int, Int>, blueBallLocation: Pair<Int, Int>, map: Array<CharArray>, direction: Pair<Int, Int>): Boolean {
    var nextBlueLocation = blueBallLocation.first to blueBallLocation.second
    for (i in 0 until map.size.coerceAtLeast(map[0].size)) {
        nextBlueLocation = nextBlueLocation.first + direction.first to nextBlueLocation.second + direction.second
        if (nextBlueLocation == redBallLocation) {
            return true
        }
    }

    return false
}

fun moveDirection(ballPosition: Pair<Int, Int>, direction: Pair<Int, Int>, map: Array<CharArray>): Pair<Int, Int> {
    var currentBallPosition = ballPosition

    val count = map.size.coerceAtLeast(map[0].size)
    for (i in 1..count) {
        currentBallPosition = currentBallPosition.first + direction.first to currentBallPosition.second + direction.second

        if (map[currentBallPosition.first][currentBallPosition.second] == '#') {
            return currentBallPosition.first - direction.first to currentBallPosition.second - direction.second
        } else if (map[currentBallPosition.first][currentBallPosition.second] == 'O') {
            return currentBallPosition
        }
    }

    return currentBallPosition
}

반성

과거에 비해 확실히 고난이도 문제 풀이 실력이 늘었다. 그런데 구현이 너무 느리다. 그리고 변수명 currentLocation 이런 식으로 뒀다가 redBallLocation이랑 헷갈려서 거기서 시간을 많이 뺏겼다.

변수명을 최대한 의미 있게 두려고 노력하자.