Uknow's Lab.
article thumbnail

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

 

20058번: 마법사 상어와 파이어스톰

마법사 상어는 파이어볼과 토네이도를 조합해 파이어스톰을 시전할 수 있다. 오늘은 파이어스톰을 크기가 2N × 2N인 격자로 나누어진 얼음판에서 연습하려고 한다. 위치 (r, c)는 격자의 r행 c

www.acmicpc.net

 

난이도 : 골드 3
태그 : 구현, 그래프 이론, 그래프 탐색, 너비우선탐색, 시뮬레이션, 깊이우선탐색

 

 

설명

삼성 기출 문제로 유명한 마법사 상어 시리즈 중 '마법사 상어와 파이어스톰' 문제입니다.

 

시뮬레이션 문제로써,

1. 파이어스톰을 n번 시전

2. 모든 파이어스톰 시전 후 가장 큰 덩어리 찾기

크게 두 파트로 나눌 수 있습니다.

파이어스톰 시전 과정은 단순 구현이지만

모든 파이어스톰을 시전한 후 가장 큰 덩어리를 찾는 과정에서는 DFS 혹은 BFS가 필요합니다.

 

 

 

 

소스코드

 

파이어스톰 - 시계 방향 회전

 for (i in 0 until n) {
        for (j in 0 until n) {
            copiedMap[i][j] = arr[n - 1 - j][i]
        }
    }

 

맵을 시계방향 회전하는 것은 위와 같이 단순하게 구현할 수 있습니다.

 

2^n 크기의 맵을 시계 방향으로 회전하는 부분입니다.

결과용 맵으로 쓸 copiedMap을 하나 생성해놓아야 합니다.

그냥 원본 맵을 그대로 회전하게 될 경우, 이전에 회전된 결과값을 또 다시 회전시킬 수 있기 때문입니다.

 

하지만, 해당 문제에서는 2^n 크기로 맵 구역을 나누고, 모든 구역을 시계방향으로 회전시켜야 하기에, 조금 변형을 줄 수 있겠습니다.

 

 

 

 

val copiedMap = Array(squareN) { Array(squareN) { 0 } }

for (startX in 0 until squareN step size) {
    for (startY in 0 until squareN step size) {
        for (i in 0 until size) {
            for (j in 0 until size) {
                copiedMap[i + startX][j + startY] = map[size - 1 - j + startX][i + startY]
            }
        }
    }
}

 

참고 - squaureN은 n * n, size는 마법사 상어가 파이어 스톰을 시전한 단계, 2^L1 입니다.

 

코틀린에서 step은 C/자바의 for문에서 for(int i = 0; i<n; i+= size) 중, i+=size에 대응된다고 볼 수 있습니다.

startX, startY가 0부터 squreN ( =n*n)을 size 만큼 증가시키면서,

모든 구역에 대해 시계방향 회전을 수행합니다.

 

 

 

파이어스톰 : 얼음이 3개 이상 접해있지 않은 칸 찾기

val minus = Array(squareN) { Array(squareN) { false } }

repeat(squareN) { x ->
    repeat(squareN) { y ->
        var cnt = 0

        for (i in 0 until 4) {
            val nx = x + dx[i]
            val ny = y + dy[i]

            if (nx !in 0 until squareN || ny !in 0 until squareN || copiedMap[nx][ny] == 0) continue
            cnt++
        }

        if (cnt < 3) minus[x][y] = true
    }
}

repeat(squareN) { x ->
    repeat(squareN) { y ->
        map[x][y] = copiedMap[x][y]
        if (minus[x][y] && map[x][y] > 0) map[x][y]--
    }
}

 

저 같은 경우는 얼음이 3개 이상 접해있지 않은 곳을 체크하기 위해 2차원 boolean 배열을 사용했는데요.

동서남북 4방향의 얼음을 카운트하고 3 미만이면 1씩 감소시킵니다.

 

 

DFS : 가장 큰 얼음 덩어리 찾기

var cnt = 0
var max = 0


fun main()
-- 코드 생략 --

repeat(squareN) { x ->
    repeat(squareN) { y ->
        dfs(x, y)
        cnt = 0
    }
}
    
-- 코드 생략 --
}



fun dfs(x: Int, y: Int) {
    if (map[x][y] == 0 || visited[x][y]) return
    visited[x][y] = true
    cnt++
    max = maxOf(max, cnt)

    for (i in 0 until 4) {
        val nx = x + dx[i]
        val ny = y + dy[i]

        if (nx !in 0 until squareN || ny !in 0 until squareN) continue

        dfs(nx, ny)
    }
}

 

모든 파이어스톰 시전이 끝나면, 가장 큰 덩어리를 찾아야 하는데요.

이는 간단하게 dfs 혹은 bfs를 사용하여 구하면 됩니다.

 

cnt는 한 덩어리가 얼마나 큰지 확인할 변수,

max는 가장 큰 덩어리의 크기를 저장할 변수입니다.

 

모든 정점에서 dfs를 수행하면서,

얼음이 아닌 곳(map[x][y] == 0) 혹은 이미 방문한 정점(visited[x][y] == true) 이라면 return을 통해 빠져나오고,

한 덩어리를 탐색할 때 마다 cnt를 1씩 증가시키며, 매번 max를 최댓값으로 갱신합니다.

 

 

 

 

전체 소스코드

import java.util.StringTokenizer
import kotlin.math.pow

val dx = arrayOf(1, -1, 0, 0)
val dy = arrayOf(0, 0, 1, -1)

lateinit var visited: Array<Array<Boolean>>
lateinit var map: Array<Array<Int>>
var max = 0
var squareN = 0
var cnt = 0

fun main() = with(System.`in`.bufferedReader()) {
    val (n, q) = readLine().split(" ").map { it.toInt() }
    squareN = 2.0.pow(n).toInt()

    map = Array(squareN) {
        val st = StringTokenizer(readLine())
        Array(squareN) { st.nextToken().toInt() }
    }

    val st = StringTokenizer(readLine())
    while (st.hasMoreTokens()) {
        val l = st.nextToken().toInt()

        val size = 2.0.pow(l).toInt()

        val copiedMap = Array(squareN) { Array(squareN) { 0 } }

        for (startX in 0 until squareN step size) {
            for (startY in 0 until squareN step size) {
                for (i in 0 until size) {
                    for (j in 0 until size) {
                        copiedMap[i + startX][j + startY] = map[size - 1 - j + startX][i + startY]
                    }
                }
            }
        }

        val minus = Array(squareN) { Array(squareN) { false } }

        repeat(squareN) { x ->
            repeat(squareN) { y ->
                var cnt = 0

                for (i in 0 until 4) {
                    val nx = x + dx[i]
                    val ny = y + dy[i]

                    if (nx !in 0 until squareN || ny !in 0 until squareN || copiedMap[nx][ny] == 0) continue
                    cnt++
                }

                if (cnt < 3) minus[x][y] = true
            }
        }

        repeat(squareN) { x ->
            repeat(squareN) { y ->
                map[x][y] = copiedMap[x][y]
                if (minus[x][y] && map[x][y] > 0) map[x][y]--
            }
        }
    }

    visited = Array(squareN) { Array(squareN) { false } }

    val sum = map.sumOf { it.sum() }
    repeat(squareN) { x ->
        repeat(squareN) { y ->
            dfs(x, y)
            cnt = 0
        }
    }
    println("$sum\n$max")
}

fun dfs(x: Int, y: Int) {
    if (map[x][y] == 0 || visited[x][y]) return
    visited[x][y] = true
    cnt++
    max = maxOf(max, cnt)

    for (i in 0 until 4) {
        val nx = x + dx[i]
        val ny = y + dy[i]

        if (nx !in 0 until squareN || ny !in 0 until squareN) continue

        dfs(nx, ny)
    }
}

 

 

 

 

후기

다소 복잡한 시뮬레이션 문제였으나,

천천히 차근차근 요구사항에 맞게 구현해보니, 그리 어렵지 않게 풀 수 있었습니다.

삼성 기출문제 중 하나던데, 이런 문제만 나왔으면 좋겠네요 ㅎㅎ....

profile

Uknow's Lab.

@유노 Uknow

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