본문 바로가기

백준 PS일지/DFS&BFS

[백준/Swift] 16234 : 인구이동

 

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

 

16234번: 인구 이동

N×N크기의 땅이 있고, 땅은 1×1개의 칸으로 나누어져 있다. 각각의 땅에는 나라가 하나씩 존재하며, r행 c열에 있는 나라에는 A[r][c]명이 살고 있다. 인접한 나라 사이에는 국경선이 존재한다. 모

www.acmicpc.net

 

16234 : 인구이동 / 문제 소개

 

N*N크기에서 각 1*1 칸의 땅은 '나라'가 존재한다.

이 칸마다 고유한 인구수가 존재한다.

 

국경선이 열리면 그 인접한 나라들 끼리는 연합을 이룬다

이때 인구이동이 시작된다. 그리고 연합에 포함되는 국가의 인구수는 (연합의 인구수) / (연합을 이루고 있는 칸의 개수) 인구수 만큼 된다.

 

두 나라의 인구수 차이가 L이상 R이하인 나라가 더이상 존재하지 않아 연합을 이룰 수 없을 때 비로소 인구 이동은 막을 내린다.

 

   문제에서 주어진 키워드

  • 나라에는 A[r][c]( = x명)이 살고 있음
  • 나라 간 국경선 존재
  • (인구이동)조건에 의해 국경선 열릴 수 있음
  • 연합 ( 두 나라간 차이가 L보다 크거나 같고 R보다 작거나 같을 때!!)
  • 인구 이동은 며칠 동안 발생하는가?

 

 

풀이 과정

 

맨 처음에 이 문제가 어떻게 bfs로 풀어나갈 수 있는지 고민을 했습니다.

여기서  (1,0) = 15 와 (1,1) = 30 두 나라간 국경선은 존재하지만 타 나라에 의해 연결 되었기 때문에 한개의 연합으로 간주됩니다.

따라서 위 조건에 의해 ( 10 + 15 + 20 + 20 + 30 + 25 + 22 ) / 7 = 20. 연합 국가에 속한 국가들은 모두 인구수가 20이 됩니다.

이때 초기 상태에Day1을  진행 할 수 있도록 최소 두 나라 이상이 연합을 맺을 수 있는 나라를 찾는 방법이 필요합니다.

 

첫째 날이 지난 후에

또 나라간 연합이 존재하므로 Day2 가 되면서 인구이동이 됩니다.

(20 + 20 + 10) / 3 = 16

더이상 L <= 두 나라간 차이 <= R 인 나라가 존재 하지 않기 때문에 인구 이동은 일어나지 않습니다.

고로 답은 2가 됩니다.


어떻게 이 문제의 국경선을 체크 할 것인가? 한 나라는 최소 2개~ 4개의 국경선을 가질 것인데.. 고민을 했었습니다.

하지만 생각보다 간편하게 해결할 수 있었습니다. 단순히 국경선이 1개라도 오픈 되어있는 나라라면 타 나라와 인접하고 결국 연합 국가가 된다는 것은 bfs탐색을 통해 어느 특정 나라만 지목했을 때 (인구이동 조건에 맞는 L <= 나라간 인구 차이 <= R )연합 국가가 존재하면 한 번의 bfs탐색을 통해 가능하다는 것입니다. +_+


이 문제를 풀어나가기 위해서는 '연합'가능성이 있는 나라파악하는 것입니다. 

Day0 에서 Day1로 가기 위해서는 반드시 연합 국가가 존재해야 합니다.

연합 국가는 최소 1개~x개가 될 수 있습니다.

 

연합 가능성이 있는 나라 파악을 할 때 저는 단순하게 맵의 완전 탐색을 해나가면서 만약 특정 위치에서 이웃간 인구수 차이가 L R(인구이동) 조건에 맞다면 bfs탐색을 하고 bfs 탐색을 하면서 queue에 저장시킨 좌표들을 따로 리스트에 저장해 두는 방식으로 구현을 했습니다.

또한 bfs탐색을 한 좌표는 다시 탐색 못하게 방문체크를 했습니다.

//실제 코드와 달리 많이 생략됨

여기서 queueList는 한번의 bfs 탐색을 한 후에 queue에 남아있는 연합 국가의 정보가 들어있는데 이걸 queueList에 저장했다.

Day0에서 Day1로 가게 된다면 queueList에는 특정 연합 국가들의 좌표들이 존재할 것이다!!!

	for y in 0..<land.n
        {
            for x in 0..<land.n
            {
                if !visited[y][x]
                {
                    for (dx,dy) in direction
                    {
                        let (nx,ny) = (x + dx, y + dy)
                        if nx < 0 || nx > land.n - 1 || ny < 0 || ny > land.n - 1
                        {
                            continue
                        }
                        let rc = abs(land.map[ny][nx] - land.map[y][x])
                        if rc >= land.minMove && rc <= land.maxMove
                        {
                            bfs(land, &visited, &queue)
                            break
                        }
                    }
                    queueList.append(queue)
                }
            }
        }

이때 큐 또한 사용하기 쉽게 클래스로 만들었는데..

typealias Element = (Int,Int)
/**
 * totalSum은 연합 국가가 된 모든나라의 합을 저장한다+_+
 */
class Q
{
    var queue    : [Element]
    var index    : Int
    var totalSum : Int
    init()
    {
        queue    = [Element]()
        index    = 0
        totalSum = 0
    }
}

여기서 queue라는 배열의 removeFirst() 함수를 사용하지 않고 index를 훑어 나갔다..

while queue.count != queue.index { ... }

다음에 큐의 위치를 탐색할 때 index 만 0으로 초기화 해주면 연합 나라들을 다시 탐색할 수있는데 이미 totalSum으로 연합국가를 찾는 즉시 더해갔기 때문에 이 과정은 필요가 없어졌다..

 

위 과정을 반복하면 결국에는

L R 조건에 부합하는 연합 나라가 존재하지 않고 그 결과.. Day가 답이 되는 것이다.

 

코드 구현

 

아직 제 실력이 많이 부족해 코드가 깁니다 ㅠㅠ 

import Foundation

//x,y 좌표
typealias Element = (Int,Int)

//탐색 방향
let direction     = [(-1,0),(1,0),(0,1),(0,-1)]

/**
 * @param queue     : '연합'에 속한 나라들의 좌표가 저장됨
 * @param index     : queue를 탐색하기 위한 index (재사용 good)
 * @param totalSum  : 연합에 속한 나라들을 탐색해 갈때 즉시 totalSum으로 연합 에 포함되는 인구수 다 더함.
 *                  추후 queue.count를 통해 연합 국가 개수 파악 가능!!
 */
class Q
{
    var queue    : [Element]
    var index    : Int
    var totalSum : Int
    init()
    {
        queue    = [Element]()
        index    = 0
        totalSum = 0
    }
}

/**
 * N x N 땅의 정보가 들어있습니다.
 *
 * @param n               : 나라 크기
 * @param minMove  : L조건 최소 이동 가능한 인구수
 * @param maxMove : R조건 최대 이동 가능한 인구수
 * @param map          : 나라들 인구수 있음
 */
class mapInfo
{
    let n       : Int
    let minMove : Int
    let maxMove : Int
    var map     : [[Int]]
    init()
    {
        let input = readLine()!.split(separator: " ").map{Int(String($0))!}
        n       = input[0]
        minMove = input[1]
        maxMove = input[2]
        map     = Array(repeating: [Int](), count: n)
        for i in 0..<n
        {
            map[i] = readLine()!.split(separator: " ").map{Int(String($0))!}
        }
    }
}

/**
 * 아래 메인 문장에서 연합국가의 조건이 된다면 bfs탐색을 통해 q에 연합 국가 좌표들 모두 저장
 * visited 를 통해 q에 중복되는 나라 좌표는 있을 수가 없음
 * 또한 q의 totalSum에 방문되는 연합 국가 인구수를 모조리 더함!!
 */
func bfs(_ land : mapInfo, _ visited : inout [[Bool]], _ q : inout Q)
{
    let (x,y) = q.queue[q.index]
    q.totalSum += land.map[y][x]
    
    while(q.queue.count != q.index)
    {
        let (curX,curY) = q.queue[q.index]
        q.index += 1
        for (dx,dy) in direction
        {
            let (nx,ny) = (curX + dx, curY + dy)
            if nx < 0 || nx > land.n - 1 || ny < 0 || ny > land.n - 1
            {
                continue
            }
            if !visited[ny][nx]
            {
                let rc = abs(land.map[ny][nx] - land.map[curY][curX])
                if rc >= land.minMove && rc <= land.maxMove
                {
                    q.totalSum += land.map[ny][nx]
                    
                    visited[ny][nx] = true
                    q.queue.append((nx,ny))
                }
            }
        }
    }
}
/**
 * @param land         : 국가 인구수들 저장됨
 * @param day          : 날
 * @param canMove : 국가들 간 연합 조건에 부합되는 국가가 있는가? 있다면 true
 */
func BOJ_16234()
{
    var land = mapInfo()
    var day  = 0
    var canMove = true
    /**
     * while문 시작하자마자 canMove를 false 함으로 나라들 모두 탐색했을 때 canMove = true가 되지 않으면
     * 인구이동 할 연합국가가 없는것으로 간주. 종료됨.
     *
     * 매번 queueList를 초기화 해주는데 이때마다 새로 연합국가가되는 나라들의 좌표들을 갖는 n개의 연합 들이 저장됨
     */
    while canMove
    {
        var visited   = Array(repeating: Array(repeating: false, count: land.n), count: land.n)
        var queueList = [Q]()
        canMove       = false
        for y in 0..<land.n
        {
            for x in 0..<land.n
            {
                if !visited[y][x]
                {
                    var queue = Q()
                    //이 flag는 연합국가가 있다면? true로 하고 나중에 queueList에 bfs로 연합국가를 탐색한 후에 queue를 집어 넣기 위한 변수
                    var flag  = false
                    for (dx,dy) in direction
                    {
                        let (nx,ny) = (x + dx, y + dy)
                        if nx < 0 || nx > land.n - 1 || ny < 0 || ny > land.n - 1
                        {
                            continue
                        }
                        let rc = abs(land.map[ny][nx] - land.map[y][x])
                        if rc >= land.minMove && rc <= land.maxMove
                        {
                            flag      = true
                            canMove   = true
                            visited[y][x] = true
                            queue.queue.append((x,y))
                            bfs(land, &visited, &queue)
                            break
                        }
                    }
                    if flag == true
                    {
                        queueList.append(queue)
                    }
                }
            }
        }
        /**
         * 만약 canMove가 true면 연합국가가 존재하는 것이므로
         * 연합국가의 평균값을 land.map에 새로 할당!!
         */
        if canMove
        {
            day += 1
            for i in 0..<queueList.count
            {
                queueList[i].totalSum = queueList[i].totalSum/queueList[i].queue.count
                queueList[i].index = 0
                while queueList[i].queue.count != queueList[i].index
                {
                    let (curX,curY) = queueList[i].queue[queueList[i].index]
                    queueList[i].index += 1
                    land.map[curY][curX] = queueList[i].totalSum
                }
            }
        }
    }
    print(day)
}
BOJ_16234()

 

'백준 PS일지 > DFS&BFS' 카테고리의 다른 글

[백준/Swift] 6593 : 상범 빌딩  (0) 2022.07.04
[백준/Swift] 5014 : 스타트링크  (0) 2022.07.02
[백준/Swift] 2146 : 다리 만들기  (0) 2022.06.29
[백준/Swift] 5427 : 불  (0) 2022.06.29