Problem Solving

[Swift] BOJ 11967 불켜기 풀이

DuncanKim 2024. 9. 20. 00:57
728x90

[Swift] BOJ 11967 불켜기 풀이

 

 

1. 문제 분석

 

일단 이 문제는 문제를 잘 읽어야 한다는 것을 "맞았습니다!"를 받고 알게 되는 문제이다. 대충 시간복잡도랑 불 켤 수 있는 방을 찾으면 되겠다해서 BFS를 기계적으로 구현하면 바로 틀리게 된다.


BFS 투어를 하고 있던 나는 순간이동 같은 개념인줄 알고 1, 1에서 1, 3으로 가고... 뭐 이런식으로 생각하고 처음 코드를 짰다가 빨간 "틀렸습니다"를 맛보게 되었다. 그런데 그 후에도, 스위치를 켠 방은 무조건 건너갈 수 있을 것이다라고 생각하고 코드를 짜버려서 또 틀렸습니다를 마주했다.

 

일단은 BFS로 현재 갈 수 있는 곳을 탐색해야 해서 이동하는 것은 맞다. 그런데 중요한 것은 암소가 "불이 켜진 방"으로만 갈 수 있다는 사실이다. 1, 1 방에서 1, 3의 불을 켤 수는 있다. 왜냐하면 1, 1은 항상 불이 켜져있으니까. 그런데, 그 후 다른 방으로 가야할 때, 다른 방들은 불이 켜져 있지 않으면 아무데도 갈 수 없으며 불을 켤 수 없다. 이 부분을 조심해야 한다. 그런데 최종적으로 구해야 하는 것은 "불이 켜진 방의 수"이다. 이 부분을 정확히 하고 가야 한다.

 

 

2. 풀이 생각

 

그렇다면, 일단은 1, 1에서 시작하게 된다. 1, 1에서 어떤 방의 불을 켤 수 있는지를 정리하는 3차원 배열을 두고, x, y를 인덱스로 사용해서 접근을 하는 방식으로 "x, y 위치에서 불을 켤 수 있는 방의 리스트"를 관리하기로 한다. 3차원 배열 graph[x][y]에는 (x: Int, y: Int)라는 튜플이 들어가게 되고, 어느 방에 들어갔을 때, 이 배열을 순회하면서 튜플이 가리키는 좌표의 방에 불을 켜주면 되는 것이다.

 

이를 위해 불을 켰는지를 확인할 수 있는 lit와 bfs에서 방문의 여부를 알 수 있는 visited Bool 배열을 둔다. 보통은 visited를 하나만 두지만, 여기에서는 불이 켜졌는지 여부에 따라 방문할 수 있는지가 갈리기 때문에 필요한 배열이었다. 

 

한편, 불이 켜졌지만, 당장은 방문할 수 없는 경우를 대비해야할 필요가 있었다. 그래서 당장은 불이 켜졌지만, 아직 방문할 수 없는 곳들을 따로 저장해두는 배열도 두고, 모든 접근 가능한 방들을 방문하고 나서 다시 못갔던 방까지 갈 수 있는지를 확인해주고 이 방에 스위치가 있는지를 확인해주는 방식으로 풀이를 생각했다.

 

 

3. 코드

 

func boj_11967() {
    let input = readLine()!.split(separator: " ").map { Int($0)! }
    let (n, m) = (input[0], input[1])
    let directions = [(-1, 0), (1, 0), (0, 1), (0, -1)]

    var graph = [[[(x: Int, y: Int)]]](repeating: [[(x: Int, y: Int)]](repeating: [(x: Int, y: Int)](), count: n + 1), count: n + 1)
    var visited = [[Bool]](repeating: [Bool](repeating: false, count: n + 1), count: n + 1)
    var lit = [[Bool]](repeating: [Bool](repeating: false, count: n + 1), count: n + 1)
    var queue = [(x: Int, y: Int)]()
    var waiting = [(x: Int, y: Int)]()  // 불이 켜졌지만 갈 수 없는 방들
    var result = 1
    
    for _ in 0..<m {
        let read = readLine()!.split(separator: " ").map { Int($0)! }
        graph[read[1]][read[0]].append((read[2], read[3]))
    }

    // 시작점
    queue.append((1, 1))
    visited[1][1] = true
    lit[1][1] = true
    
    while !queue.isEmpty {
        let point = queue.removeFirst()

        // 불을 켜는 작업
        for room in graph[point.y][point.x] {
            if !lit[room.y][room.x] {
                lit[room.y][room.x] = true
                result += 1
                waiting.append((room.x, room.y))  // 불이 켜졌지만 아직 방문 불가
            }
        }

        // 인접한 방들로 이동
        for dir in directions {
            let newX = point.x + dir.0
            let newY = point.y + dir.1
            
            if newX > 0 && newY > 0 && newX <= n && newY <= n && lit[newY][newX] && !visited[newY][newX] {
                visited[newY][newX] = true
                queue.append((newX, newY))
            }
        }

        // 불이 켜졌지만 갈 수 없던 방 다시 체크
        var i = 0
        while i < waiting.count {
            let room = waiting[i]
            var canVisit = false
            for dir in directions {
                let adjX = room.x + dir.0
                let adjY = room.y + dir.1
                if adjX > 0 && adjY > 0 && adjX <= n && adjY <= n && visited[adjY][adjX] {
                    canVisit = true
                    break
                }
            }
            if canVisit {
                visited[room.y][room.x] = true
                queue.append(room)
                waiting.remove(at: i)
            } else {
                i += 1
            }
        }
    }

    print(result)
}

 

 

굉장히 복잡한데, 위에서 설명한 풀이를 최대한 구현해놓은 것이다. BFS를 기본적으로 사용하면서 여러 케이스들을 떠올리고 구현을 하면 되는 문제였다. 다만, 이런 구현은 처음이라 시간이 굉장히 오래걸렸다.

 

BFS를 심화하여 사용하는 문제를 몇 번 더 풀어보면 푸는 감이 조금은 올 것 같다.

 

 

728x90