백준 1781 컵라면

임찬형·2022년 11월 1일
0

문제 팁

목록 보기
63/73

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

N개의 문제가 주어지고, 각 문제는 데드라인과 보상인 컵라면 개수가 주어진다.

문제를 풀어 받을 수 있는 컵라면의 최대 개수를 출력하는 문제이다.

문제들은 데드라인이 있어, 데드라인이 X인 문제는 1일~X일 사이에 풀 수 있다.

이 점을 생각해 그리디하게 문제를 풀 수 있는 알고리즘을 생각해보았다.

1. 먼저 입력받은 문제들을 데드라인의 내림차순으로 (같을 경우 보상이 많은 순서대로) 정렬한다.

데드라인 내림차순으로 한 이유는 데드라인이 긴 문제의 경우 최대한 데드라인에 맞춰 푸는 것이 최적의 경우가 될 것 같다 생각했기 때문이다.

따라서 내림차순을 통해 데드라인이 긴 (같으면 컵라면이 많은) 문제들부터 풀도록 하였다.

2. 최대 reward(컵라면)를 반환하는 Priority Queue를 준비한다.
현재 문제의 deadline 날짜에 이미 다른 문제를 푼 경우(더 높은 reward) 해당 문제는 PQ에 넣어 다른 날짜에 풀 수도 있도록 하기 위해서이다.

1번 예시와 동일하게 (1 3) (2 10) (2 20)이 들어오면, (2 20)을 2일에 풀고, (2 10)은 PQ에 넣은 뒤 1일에 (1 3)을 PQ에 넣고 (2 10)을 꺼내 풀게 된다.

3. 날짜를 1일씩 줄여 가며 다음 과정을 반복한다.
현재 날짜 <= 현재 문제 deadline인 경우:
현재 문제를 pq에 넣고, 현재 문제의 deadline에 문제를 풀었는지 확인한 뒤, 안풀었으면 해당 날짜에 풀고 풀었으면 다음 문제로 이동.

현재 날짜 > 현재 문제 deadline인 경우:
현재 날짜에 pq에서 꺼내온 문제를 풀고 날짜를 감소시킴.

위 과정을 모든 문제에 대해 진행한 뒤, 남아 있는 day만큼 pq에서 꺼내 와 풀어 reward(컵라면)를 추가함.

예제를 통해 알고리즘을 따라가 보자.
7
6 2
4 5
4 4
4 2
3 1
1 7
1 6

위와 같은 케이스가 있다고 하자. (deadline, reward 내림차순 했다 가정)

  1. day 6에서 시작한다.
    (6 2)에 대해, 6 <= 6이므로 PQ에 (6 2)를 넣고 6일차에 푼 문제가 없으므로 PQ에서 꺼내 6일차에 reward 2를 획득하고 day-- idx++

  2. day 5
    (4 5)에 대해, 5 > 4이므로 PQ에 넣지 않음.
    5일차에 푼 문제가 없으므로 PQ에서 문제를 꺼내 와 5일차에 풂. 단 PQ가 비었으므로 reward를 추가하지는 않고 day--

  3. day 4
    (4 5)에 대해, 4 <= 4이므로 PQ에 (4 5)를 넣고 4일차에 푼 문제가 없으므로 PQ에서 꺼내 4일차에 reward 5를 획득하고 day-- idx++

  4. day 3
    (4 4)에 대해, 3 <= 4이므로 PQ에 (4 4)를 넣음. 4일차에 이미 reward 5짜리 문제를 풀었으므로 PQ에 넣기만 한 상태로 idx++

  5. day 3
    (4 2)에 대해, 4번 과정과 동일하게 PQ에 (4 2)만 넣고 idx++

  6. day 3
    (3 1)에 대해, 3 <= 3이므로 PQ에 (3 1)을 넣음. 3일차에 푼 문제가 없으므로 PQ에서 (4 4)를 꺼내 3일차에 풀어 reward 4 획득하고 day-- idx++

  7. day 2
    (1 7)에 대해 2 > 1이므로 PQ에 넣지 않음. 2일차에 푼 문제가 없으므로 PQ에서 (4 2)를 꺼내 2일차에 풀어 reward 2 획득. day--

  8. day 1
    (1 7)에 대해 1 <= 1이므로 PQ에 넣음. 1일차에 푼 문제가 없으므로 PQ에서 (1 7)을 꺼내 1일차에 풀어 reward 7 획득. day--

  9. 남은 day가 0이므로 추가 작업은 필요하지 않음. answer 20 출력.

fun main(args: Array<String>): Unit = with(System.`in`.bufferedReader()) {
    val N = readLine().toInt()
    val problems = Array(N){
        val (dl, cup) = readLine().split(' ').map{it.toInt()}
        Problem(dl, cup)
    }.sortedWith{ p1, p2 ->
        if(p1.deadline == p2.deadline) p2.reward - p1.reward
        else p2.deadline - p1.deadline
    }

    val pq = PriorityQueue<Problem>{p1, p2 -> p2.reward - p1.reward}

    var answer = 0
    var day = problems.maxOf{it.deadline}
    var idx = 0
    val dayCheck = IntArray(N + 1){0}
    while(idx < problems.size){
        val p = problems[idx]

        if(day <= p.deadline){
            pq.offer(p)
        }else{
            pq.poll()?.let {
                dayCheck[day] = it.reward
                answer += it.reward
            }
            day--
            continue
        }

        if(dayCheck[p.deadline] == 0){
            pq.poll()?.let {
                dayCheck[p.deadline] = it.reward
                answer += it.reward
            }
            day = p.deadline - 1
        }
        idx++
    }

    while(day > 0){
        pq.poll()?.let {
            dayCheck[day--] = it.reward
            answer += it.reward
        }
    }

    print(answer)
}
data class Problem(
    val deadline: Int,
    val reward: Int
)

+) 추가로
얼마 전에 풀었던 다른 그리디 문제와 유사한 방법이 생각나서 적어본다.

입력 문제들을 컵라면 개수의 내림차순으로 정렬한다.

그리고나서 문제들을 순회하며, 해당 deadline 날짜에 문제를 풀지 않았다면 우선적으로 그 날짜에 풀고 해당 deadline 날짜에 풀었다면 그 날짜부터 앞으로 이동하며 빈 날짜에 푼다.

단, 이 문제에서는 N이 200,000까지 이므로 선형으로 빈 날짜를 찾는다면 시간 초과가 날 수 있다.

그래서 다음 빈 날짜를 가리키도록 union-find 알고리즘을 이용해 시간을 단축시켜 풀 수 있다.

lateinit var dt: IntArray
fun main(args: Array<String>): Unit = with(System.`in`.bufferedReader()) {
    val N = readLine().toInt()
    val problems = Array(N){
        val (dl, cup) = readLine().split(' ').map{it.toInt()}
        Problem(dl, cup)
    }.sortedWith{ p1, p2 -> p2.reward - p1.reward }

    val dayCheck = IntArray(N + 1){0}
    dt = IntArray(N + 1){it}

    for(p in problems){
        if(dayCheck[p.deadline] == 0){
            dayCheck[p.deadline] = p.reward
            union(find(p.deadline) - 1, p.deadline)
        }else{
            val parent = find(p.deadline)
            if(parent > 0) {
                dayCheck[parent] = p.reward
                union(parent - 1, p.deadline)
                find(p.deadline) // union 후 싱크 맞추기
            }
        }
    }

    print(dayCheck.sum() - dayCheck[0])
}

fun find(x: Int): Int{
    if(dt[x] == x) return x
    dt[x] = find(dt[x])
    return dt[x]
}

fun union(x: Int, y: Int){
    dt[find(y)] = find(x)
}

data class Problem(
    val deadline: Int,
    val reward: Int
)

복잡하지 않고 훨씬 좋은 방법인 것 같다.

0개의 댓글

관련 채용 정보