Kotlin/Algorithm Problems

<백준> 달이 차오른다, 가자.(Gold 1)

re트 2024. 4. 1. 21:13
728x90

[백준]

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

[깃허브]

https://github.com/heesoo-park/ForCodeKata/tree/main/baekjoon%20%EB%AC%B8%EC%A0%9C%EC%A7%91/%EB%8B%AC%EC%9D%B4%20%EC%B0%A8%EC%98%A4%EB%A5%B8%EB%8B%A4,%20%EA%B0%80%EC%9E%90.

제출 결과

 

최종 프로젝트가 마무리되고 드디어 다시 알고리즘 문제를 풀 여유가 생겼다.

물론 밀렸던 집안일 하랴... 이력서 준비하랴... 이것저것 하다보니 아침에 하려고 했던 일정이 망가져서 밤 늦게 하고 있지만 다시 손을 대서 할 수 있다는게 어디인가 싶다.(감사한 마음 뿐...)

 

그렇게 오늘 손을 댄 문제는 친구에게 추천받은 백준 문제였다.

내가 BFS, DFS, DP에 너무 약하다고 하니까 바로 추천해준 문제로 기본 BFS에 비트마스킹까지 섞여있다고 미리 말해주더라

 

BFS만 있었으면 좀 겁을 덜 먹었을텐데 비트마스킹이 있다고 하니까 겁을 더 먹었다.

※ 비트마스킹
  정수의 이진수 표현을 자료구조로 쓰는 기법

- 비트 연산자
a and b : a의 모든 비트와 b의 모든 비트를 AND 연산
a or b : a의 모든 비트와 b의 모든 비트를 OR 연산
a xor b : a의 모든 비트와 b의 모든 비트를 XOR 연산
a.inv() : a의 모든 비트에 NOT 연산
a shl 1 : a의 모든 비트를 왼쪽으로 1칸 이동
a shr 1 : a의 모든 비트를 오른쪽으로 1칸 이동

- 응용하는 영역
집합의 표현, 원소 추가, 원소 삭제, 원소 토글, 집합 연산, 부분 집합 순회 등...

 

문제를 보면서 다른 것보다 가장 고민이 되는 건 '어떻게 찾은 열쇠를 저장하고 분기를 나눠야하나?'였다.

뭔가 이부분이 비트마스킹을 쓰는 거 같은데 감이 잘 오지 않았다.

그래서 이번에는 고민을 하다가 바로 블로그를 돌아다녔다.

 

그렇게 해서 알게 된 건 visited 변수를 3차원 배열로 사용하는 것과 열쇠 하나하나를 한 비트로 취급해서 비트마스킹을 사용하는 것이었다.

그렇게 하니까 열쇠 개수에 따른 모든 분기를 저장할 수 있게 되더라

전에도 이런 문제를 봤었던 거 같은데 기억이 제대로 나지는 않는다.

풀기도 했었던 거 같은데...

 

익숙한 BFS의 형태였고 다른 것은 비트 연산자를 사용하는 부분이었다.

keys and (1 shl (maze[new_r][new_c] - 'A' + 1))
keys or (1 shl (maze[new_r][new_c] - 'a' + 1))

또한 Data Class를 사용하는 부분이었다.

이런 문제(관리해야하는 프로퍼티가 여러개)를 풀 때는 Data Class를 사용하는 게 확실히 편리한 거 같다.

 

또한 한 줄에 두 개의 값이 있을 때 한번에 받는 방법도 알게 되었다.

val (n, m) = readLine().split(' ').map { it.toInt() }

 

출구에 도착하면 현재 cnt에 1을 더해 반환하고

문을 만나면 열쇠가 있을 때 큐에 추가하고

열쇠를 찾으면 열쇠 리스트를 갱신한 다음 큐에 추가하고

그냥 길이면 큐에 추가한다.

 

정말 딱 비트마스킹만 추가된 문제였고 여러가지로 응용해볼만한 문제유형인 거 같다.

꼭 복습을 해야겠다.(태그에 추가완료)

 

뭔가 앞으로 이런 문제를 많이 보게 될 거 같다.

3차원 배열로 하니까 더 케이스가 넓어진다.

그리고 뭔가 열쇠가 1회용이었다면 이 문제가 더 어려웠을 거 같다는 생각이 든다.

 

해당 방법으로 작성한 코드는 다음과 같다.

import java.util.LinkedList

val dy = arrayOf(0, 1, 0, -1)
val dx = arrayOf(1, 0, -1, 0)
val visited = Array(1 shl 7) { Array(50) { BooleanArray(50) } }

// BFS에서 사용할 순간순간의 정보가 담긴 데이터 클래스
data class Pos(
    val r: Int,
    val c: Int,
    val key: Int,
    val cnt: Int
)

fun main() = with(System.`in`.bufferedReader()) {
    // 미로의 가로 세로 길이
    val (n, m) = readLine().split(' ').map { it.toInt() }
    // 민식이의 row 시작 위치
    var sr = 0
    // 민식이의 column 시작 위치
    var sc = 0
    // 미로 모양
    val maze = Array(n) { r ->
        val row = readLine()
        CharArray(m) { c ->
            var pos = row[c]
            if (row[c] == '0') {
                sr = r
                sc = c
                pos = '.'
            }
            pos
        }
    }

    println(bfs(sr, sc, n, m, maze))
    close()
}

fun bfs(sr: Int, sc: Int, n: Int, m: Int, maze: Array<CharArray>): Int {
    // 시작위치 큐에 넣음
    val q = LinkedList<Pos>()
    q.add(Pos(sr, sc, 1, 0))
    visited[1][sr][sc] = true

    while (q.isNotEmpty()) {
        // 큐에서 하나 뺌
        val cur = q.poll()
        // 네 방향 체크
        for (i in 0 until 4) {
            val new_r = cur.r + dy[i]
            val new_c = cur.c + dx[i]
            var keys = cur.key

            // 패스하는 조건들
            if (new_r !in 0 until n || new_c !in 0 until m) continue
            if (visited[keys][new_r][new_c]) continue
            if (maze[new_r][new_c] == '#') continue

            // 출구인 경우
            if (maze[new_r][new_c] == '1') return cur.cnt + 1

            if (maze[new_r][new_c] in 'A'..'F') { // 문을 만난 경우
                if (keys and (1 shl (maze[new_r][new_c] - 'A' + 1)) != 0) { // 열쇠가 있다면 큐에 추가
                    q.add(Pos(new_r, new_c, keys, cur.cnt + 1))
                    visited[keys][new_r][new_c] = true
                } else { // 열쇠가 없다면 패스
                    continue
                }
            } else if (maze[new_r][new_c] in 'a'..'f') { // 열쇠를 찾으면 열쇠 리스트 갱신하고 큐에 추가
                keys = keys or (1 shl (maze[new_r][new_c] - 'a' + 1))
                q.add(Pos(new_r, new_c, keys, cur.cnt + 1))
                visited[keys][new_r][new_c] = true
            } else { // 그 외에는 그냥 큐에 추가
                q.add(Pos(new_r, new_c, keys, cur.cnt + 1))
                visited[keys][new_r][new_c] = true
            }
        }
    }

    // 미로 탈출 방법이 없는 경우
    return -1
}

 

참고)

https://yabmoons.tistory.com/102

https://ongveloper.tistory.com/363

반응형