본문 바로가기
코딩테스트/백준 코틀린

[백준] 29. 할로윈의 양아치 20303 해설

by junjunjun 2024. 1. 23.
반응형

할로윈의 양아치

 

20303번: 할로윈의 양아치

첫째 줄에 정수 $N$, $M$, $K$가 주어진다. $N$은 거리에 있는 아이들의 수, $M$은 아이들의 친구 관계 수, $K$는 울음소리가 공명하기 위한 최소 아이의 수이다. ($1 \leq N \leq 30\ 000$, $0 \leq M \leq 100\ 000$,

www.acmicpc.net

문제

나쁜 스브러스가 k-1명 이하의 아이들을 상대로 최대의 사탕개수를 빼앗으면 되는 문제입니다.

다만 아이들은 각각 친구를 가지며 어떤 아이의 사탕을 뺏을 경우 그 아이와 친구관계로 이루어진 모든 아이의 사탕까지 뺏어줘야 됩니다.

풀이

백준 Class 순서에 맞게 문제를 풀어왔기 때문에 해당 문제는 어렵지 않게 풀 수 있었습니다.

 

K-1명 이하의 친구들을 상대로 최대의 사탕개수를 빼앗아주면 된다.

= K kg 배낭에 최대 가치의 물건을 넣는다.

결국에는 배낭 문제와 동일합니다.

 

다만 아이의 사탕을 뺏을 때 해당 아이의 친구관계인 모든 아이까지 사탕을 뺏어야 되기 때문에 관련 있는 아이들끼리 그룹화를 해줘서 진행해 주면 됩니다. 그룹화는 bfs, dfs, union-find 등 상관없이 그룹만 만들어주면 됩니다.

 

 

두 개의 코드가 있지만 두 번째 코드가 더 효율적입니다.

 

첫 번째 코드 bfs + 2차원 배열 DP

그룹화 과정은 bfs를 이용하였고 최대 사탕을 얻는 과정은 기존 배낭문제의 풀이 방식 그대로 이용해 주었습니다.

import java.io.StreamTokenizer
import java.util.LinkedList

fun main() = StreamTokenizer(System.`in`.bufferedReader()).run {
    fun input(): Int {
        nextToken()
        return nval.toInt()
    }
    val N = input() // 아이들 수
    val M = input() // 아이들의 친구 관계 수
    val K = input() // 울음 소리가 공명하기 위한 최소 아이의 수

    val candy = IntArray(N+1)
    for (i in 1..N) candy[i] = input()
    val arr = Array(N+1) { arrayListOf<Int>() }

    // 친구 관계
    repeat(M) {
        val a= input()
        val b= input()
        arr[a].add(b)
        arr[b].add(a)
    }

    // 친구끼리 그룹을 만들어줌 bfs
    data class Group(val count: Int, val sum: Int)  // 인원수, 캔디 합
    val visited = BooleanArray(N+1)
    val group = arrayListOf<Group>()
    for (i in 1..N) {
        if (!visited[i]) {
            var count = 0
            var sum = 0
            val queue = LinkedList<Int>()

            queue.add(i)
            visited[i] = true

            while (queue.isNotEmpty()) {

                val cur = queue.poll()
                count++
                sum += candy[cur]

                for (next in arr[cur]) {
                    if (!visited[next]) {
                        queue.add(next)
                        visited[next] = true
                    }
                }
            }
            group.add(Group(count, sum))
        }
    }

    // 배낭문제와 동일
    val len = group.size
    val dp = Array(K) {IntArray(len)}
    for (i in 0 until K) {
        if (i >= group.get(0).count)
            dp[i][0] = group.get(0).sum
    }

    for (i in 0 until K) {
        for (j in 1 until len) {
            if (group[j].count > i) {
                dp[i][j] = dp[i][j-1]
            } else {
                dp[i][j] = Math.max(dp[i][j-1], dp[i-group[j].count][j-1] + group[j].sum)
            }
        }
    }
    print(dp[K-1][len-1])
}

 

 

두 번째 풀이 union-find + 1차원 배열 DP

그룹화 과정은 union-find을 이용하여 메모리를 더 아낄 수 있었습니다.

최대 사탕을 얻는 과정은 굳이 2차원 배열을 쓰지 않고도 1차원 배열만으로 해결할 수 있었습니다.

import java.io.StreamTokenizer

fun main() = StreamTokenizer(System.`in`.bufferedReader()).run {
    fun input(): Int {
        nextToken()
        return nval.toInt()
    }
    val N = input() // 아이들 수
    val M = input() // 아이들의 친구 관계 수
    val K = input() // 울음 소리가 공명하기 위한 최소 아이의 수

    val candy = IntArray(N+1)
    for (i in 1..N) candy[i] = input()

    val parents = IntArray(N+1) {it}
    val rank = IntArray(N+1)

    fun find(num : Int): Int {
        if (parents[num] == num)
            return num
        return find(parents[num])
    }
    fun union(n1: Int, n2: Int) {
        val p1 = find(n1)
        val p2 = find(n2)
        if (p1 == p2) return

        if (rank[p1] == rank[p2]) rank[p1]++
        if (rank[p1] > rank[p2]) parents[p2] = p1
        else parents[p1] = p2
    }
    // 친구끼리 그룹화
    repeat(M) {
        val a= input()
        val b= input()
        union(a, b)
    }

    // 그룹의 총 인원수와 총 캔디수를 구해줌
    val sumArr = IntArray(N+1) {candy[it]}
    val countArr = IntArray(N+1) {1}
    for (i in 1..N) {
        val p = find(i)
        if (p == i) continue
        sumArr[p] += candy[i]
        countArr[p]++
    }

    // 배낭문제와 동일하게 k-1명의 인원일 때 최대 캔디수를 구해줌
    val dp = IntArray(N+1)
    for (i in 1..N) {
        if (parents[i] != i) continue

        for (j in K-1 downTo countArr[i]) {
            dp[j] = Math.max(dp[j], dp[j - countArr[i]] + sumArr[i])
        }
    }
    print(dp[K-1])
}
반응형

댓글