Algorithm/BOJ

[Kotlin] 백준 23031 : 으어어… 에이쁠 주세요..

개발하는 곰돌이 2024. 5. 2. 14:22

문제 링크 : https://www.acmicpc.net/problem/23031

문제 해설

특별한 알고리즘이 필요하진 않은 단순 구현 문제.

 

한 번의 이동이 발생했을 때 아리와 좀비가 마주치더라도 해당 칸에 스위치가 있거나 불이 켜져있기만 하면 아리는 기절하지 않는다.

 

이로 인해 아리의 행동할 때 마다 진행되는 내용을 과정을 3가지 단계로 나누어 생각해볼 수 있다.

  1. 아리가 현재 순서에 맞는 행동을 수행한다.
  2. 아리의 현재 위치에 스위치가 있다면 스위치를 킨다.
  3. 모든 좀비들이 바라보는 방향으로 1칸 이동한다.

이제 각 순서에 맞게 구현을 해보자.


1. 아리가 현재 순서에 맞는 행동을 수행한다.

 

아리의 행동 중 방향을 바꾸는 L과 R을 생각해보자. 방향은 한 번 전환할 때 90도씩 전환하고 4번 전환하면 원래 방향을 보게 된다.

 

아리의 초기 방향이 아래쪽이니까 아래쪽을 0으로 하여 시계 방향으로 0, 1, 2, 3으로 아리의 이동 방향을 결정할 수 있다.

 

이 때, 아리가 왼쪽으로 회전하는 경우는 현재 방향에서 -1이 되는 경우지만 나머지 연산으로 방향을 구하기 위해선 +4를 한 이후 -1을 해줘야 한다.

 

아리의 위치가 다솔관 밖을 벗어나지 않도록 처리도 해준다.

var direction = 0   // 0: down, 1: left, 2: up, 3: right
var (r, c) = 0 to 0
val dr = intArrayOf(1, 0, -1, 0)
val dc = intArrayOf(0, -1, 0, 1)
for (command in move) {
    when (command) {
        'L' -> direction = (direction + 3) % 4
        'R' -> direction = (direction + 1) % 4
        'F' -> {
            val nr = r + dr[direction]
            val nc = c + dc[direction]
            if (nr in 0..<n && nc in 0..<n) {
                r = nr
                c = nc
            }
        }
    }
}

2. 아리의 현재 위치에 스위치가 있다면 스위치를 킨다.

 

아리가 이동을 마쳤으니 스위치가 있는지 검사한다.

 

만약 스위치가 있다면 해당 칸을 포함해 상하좌우 1칸씩 불을 켜주면 된다.

 

불을 켤 때 다솔관의 범위를 벗어나지 않도록 for문의 반복 범위를 maxOf()minOf()로 제한해준다.

if (dasol[r][c] == 'S') {
    for (i in maxOf(0, r - 1)..minOf(n - 1, r + 1)) {
        for (j in maxOf(0, c - 1)..minOf(n - 1, c + 1)) {
            isLighting[i][j] = true
        }
    }
}

3. 모든 좀비들이 바라보는 방향으로 1칸 이동한다.

 

이제 좀비들을 이동시킬 차례다.

 

1.에서 아리가 이동하고 2.에서 스위치를 작동시켰기 때문에 좀비들을 이동시키기 전에 아리가 불이 꺼진 상태에서 좀비와 마주쳤는지 확인해야 한다. 여기서 아리의 현재 칸에 불이 꺼져있는데 좀비와 마주쳤다면 바로 Aaaaaah!를 출력하고 프로그램을 종료한다.

if (r == zombie.r && c == zombie.c && !isLighting[r][c]) {
    println("Aaaaaah!")
    return@with
}

확인이 끝났으면 좀비들을 방향에 맞게 이동시킨다.

 

좀비는 위 또는 아래로만 움직이기 때문에 좀비의 방향은 아래일 때 1, 위일 때 -1로 표현할 수 있다. 좀비의 다음 위치가 다솔관 내부라면 좀비의 현재 위치에 좀비의 현재 방향을 더해주면 되고, 벽에 부딪힌다면 방향만 바꿔주면 된다.

if (zombie.r + zombie.direction in 0..<n) zombie.r += zombie.direction
else zombie.direction = -zombie.direction

좀비가 이동한 이후 좀비의 위치가 변경되었기 때문에 다시 한번 불이 꺼진 상태에서 아리와 마주쳤는지 확인한다.

 

  • 좀비의 이동 로직 전체
for (zombie in zombies) {
    // 아리가 기절하는지 검사
    if (r == zombie.r && c == zombie.c && !isLighting[r][c]) {
        println("Aaaaaah!")
        return@with
    }
    // 좀비 이동
    if (zombie.r + zombie.direction in 0..<n) zombie.r += zombie.direction
    else zombie.direction = -zombie.direction
    // 아리가 기절하는지 다시 검사
    if (r == zombie.r && c == zombie.c && !isLighting[r][c]) {
        println("Aaaaaah!")
        return@with
    }
}

 

모든 이동이 끝날 때 까지 프로그램이 실행되었다면 아리가 기절하지 않은 것이기 때문에 Phew...를 출력해주면 된다.

Code

fun main() = with(System.`in`.bufferedReader()) {
    val n = readLine().toInt()
    val move = readLine()
    val zombies = mutableListOf<Zombie>()
    val dasol = Array(n) { i ->
        readLine().apply { forEachIndexed { j, c ->
                if (c == 'Z') zombies.add(Zombie(i, j))
        } }
    }
    val isLighting = Array(n) { BooleanArray(n) }
    var direction = 0   // 0: down, 1: left, 2: up, 3: right
    var (r, c) = 0 to 0
    val dr = intArrayOf(1, 0, -1, 0)
    val dc = intArrayOf(0, -1, 0, 1)
    for (command in move) {
        when (command) {
            'L' -> direction = (direction + 3) % 4
            'R' -> direction = (direction + 1) % 4
            'F' -> {
                val nr = r + dr[direction]
                val nc = c + dc[direction]
                if (nr in 0..<n && nc in 0..<n) {
                    r = nr
                    c = nc
                }
            }
        }
        
        if (dasol[r][c] == 'S') {
            for (i in maxOf(0, r - 1)..minOf(n - 1, r + 1)) {
                for (j in maxOf(0, c - 1)..minOf(n - 1, c + 1)) {
                    isLighting[i][j] = true
                }
            }
        }
        
        for (zombie in zombies) {
            if (r == zombie.r && c == zombie.c && !isLighting[r][c]) {
                println("Aaaaaah!")
                return@with
            }
            
            if (zombie.r + zombie.direction in 0..<n) zombie.r += zombie.direction
            else zombie.direction = -zombie.direction
            
            if (r == zombie.r && c == zombie.c && !isLighting[r][c]) {
                println("Aaaaaah!")
                return@with
            }
        }
    }
    println("Phew...")
}

private class Zombie(var r: Int, val c: Int, var direction: Int = 1)
728x90