{ }codelper
욕심쟁이우선순위 큐(힙)P0152

마감 안에 뽑을 3D 프린터 주문 고르기

3D 프린터 한 대로 출력 주문 n개를 받았다. i번째 주문은 출력하는 데 t시간이 걸리고, d시각까지 끝나야 한다. 프린터는 0시각부터 쓸 수 있고, 한 번에 주문 하나만 출력하며, 시작한 주문은 멈추지 않고 끝까지 출력한다. 주문이 정확히 d시각에 끝나도 마감을 지킨 것이다. 받은 주문 가운데 일부를 골라, 고른 주문을 모두 마감 안에 끝내려 한다. 고른 주문을 출력하는 차례는 마음대로 정할 수 있고, 고르지 않은 주문은 출력하지 않는다. 고를 수 있는 주문의 수가 가장 많게 하라. 그런 고르기가 여럿이면, 고른 주문의 출력 시간을 모두 더한 값이 가장 작은 것을 고른다. 고른 주문의 수와 그 출력 시간의 합을 빈칸으로 나눠 한 줄에 출력하라. 하나도 고를 수 없으면 0 0을 출력한다.

입력
첫 줄에 n, 다음 n줄에 줄마다 t와 d가 빈칸으로 나뉘어 주어진다.
1 ≤ n ≤ 10,000, 1 ≤ t ≤ 1,000,000, 1 ≤ d ≤ 10,000,000,000
출력
고른 주문의 수와 출력 시간의 합을 빈칸으로 나눠 한 줄에
예시 1 · stdin
5
4 4
1 5
1 5
1 6
1 6
stdout
4 4
예시 2 · stdin
2
5 3
7 6
stdout
0 0
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

고른 주문들은 마감이 이른 것부터 이어 뽑는 것이 가장 좋아요.
그래서 주문을 마감 순으로 정렬해 하나씩 넣고, 지금까지의 시간 합이 이번 마감을 넘으면 고른 것 가운데 가장 오래 걸리는 주문 하나를 빼요(욕심 — 개수는 하나 줄어도 남은 시간이 가장 많이 줄어요).
가장 긴 것을 빨리 꺼내려면 최대 힙이 필요해요.
이렇게 하면 개수가 가장 많고, 그 가운데 시간 합도 가장 작은 고르기가 남아요.
함정 하나: 들어가지 않는 주문을 그냥 건너뛰면 틀려요 — 첫 예시에서 4시간짜리를 먼저 넣으면 1시간짜리 둘만 더 들어가 3개지만, 4시간짜리를 빼면 4개예요.
함정 둘: 짧은 주문부터 넣으면 마감이 늦은 짧은 주문이 마감이 이른 주문의 자리를 빼앗아요.
함정 셋: 시간 합은 100억까지 가고 마감도 int를 넘어서, C·C++ long long, Java·C# long이 필요해요(Java·C# int는 21억을 넘으면 음수로 감겨요.
Python·JavaScript·TypeScript·크레이콘은 그대로 돼요 — 2^53보다 훨씬 작아요).
함정 넷: 최대 힙을 얻는 법이 언어마다 반대예요.
C++ std::priority_queue는 기본이 최대 힙이라 그대로 쓰고, Java PriorityQueue는 기본이 최소 힙이라 Collections.reverseOrder()를 줘야 해요(안 주면 가장 짧은 주문을 빼요).
Python heapq는 최소 힙뿐이라 -t를 넣고 꺼낸 값의 부호를 되돌리고, C# PriorityQueue<long, long>은 우선순위를 -t로 줘요.
JavaScript·TypeScript·C·크레이콘은 힙을 목록으로 직접 만들되, 부모가 자식보다 크거나 같게 비교 방향을 바꿔요(부모 자리는 크레이콘 수학.몫(i - 1, 2)).
정렬 기준도 다르게 써요: C qsort의 비교 함수는 int를 돌려주니 long long 마감을 빼지 말고 (a > b) - (a < b)로, 크레이콘은 jobs.정렬(byDeadline)에 x[1] - y[1]을 돌려주는 함수를 넘겨요(수가 배정밀도라 빼도 돼요).

Pythonmain.py · 17 줄
import heapqimport sys data = sys.stdin.read().split()n = int(data[0])jobs = [(int(data[1 + 2 * i]), int(data[2 + 2 * i])) for i in range(n)]  # (걸리는 시간, 마감) jobs.sort(key=lambda job: job[1])  # 마감이 이른 것부터chosen = []  # 고른 주문의 시간들. heapq는 최소 힙이라 음수로 넣어 가장 긴 것을 꺼낸다total = 0  # 고른 주문을 마감 순으로 이어 뽑으면 마지막이 끝나는 시각for t, d in jobs:    heapq.heappush(chosen, -t)    total += t    if total > d:        total += heapq.heappop(chosen)  # 마감을 넘으면 고른 것 가운데 가장 긴 주문을 뺀다 print(len(chosen), total)
JavaScriptmain.js · 55 줄
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0]; // 최대 힙: heap[0]이 가장 크고, i의 자식은 2i+1, 2i+2class MaxHeap {  constructor() {    this.items = [];  }  get size() {    return this.items.length;  }  push(x) {    const a = this.items;    a.push(x);    let i = a.length - 1;    while (i > 0) {      const parent = (i - 1) >> 1;      if (a[parent] >= a[i]) break;      [a[parent], a[i]] = [a[i], a[parent]];      i = parent;    }  }  pop() {    const a = this.items;    const top = a[0];    const last = a.pop();    if (a.length > 0) {      a[0] = last;      let i = 0;      for (;;) {        const l = 2 * i + 1, r = l + 1;        let big = i;        if (l < a.length && a[l] > a[big]) big = l;        if (r < a.length && a[r] > a[big]) big = r;        if (big === i) break;        [a[big], a[i]] = [a[i], a[big]];        i = big;      }    }    return top;  }} const jobs = []; // [걸리는 시간, 마감]for (let i = 0; i < n; i++) jobs.push([data[1 + 2 * i], data[2 + 2 * i]]);jobs.sort((x, y) => x[1] - y[1]); // 마감이 이른 것부터 const chosen = new MaxHeap(); // 고른 주문의 시간들let total = 0; // 고른 주문을 마감 순으로 이어 뽑으면 마지막이 끝나는 시각for (const [t, d] of jobs) {  chosen.push(t);  total += t;  if (total > d) total -= chosen.pop(); // 마감을 넘으면 고른 것 가운데 가장 긴 주문을 뺀다}console.log(`${chosen.size} ${total}`);
TypeScriptmain.ts · 58 줄
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0]; // 최대 힙: heap[0]이 가장 크고, i의 자식은 2i+1, 2i+2class MaxHeap {  private items: number[] = [];   get size(): number {    return this.items.length;  }   push(x: number): void {    const a = this.items;    a.push(x);    let i = a.length - 1;    while (i > 0) {      const parent = (i - 1) >> 1;      if (a[parent] >= a[i]) break;      [a[parent], a[i]] = [a[i], a[parent]];      i = parent;    }  }   pop(): number {    const a = this.items;    const top = a[0];    const last = a.pop()!; // size > 0일 때만 부른다    if (a.length > 0) {      a[0] = last;      let i = 0;      for (;;) {        const l = 2 * i + 1, r = l + 1;        let big = i;        if (l < a.length && a[l] > a[big]) big = l;        if (r < a.length && a[r] > a[big]) big = r;        if (big === i) break;        [a[big], a[i]] = [a[i], a[big]];        i = big;      }    }    return top;  }} const jobs: [time: number, deadline: number][] = [];for (let i = 0; i < n; i++) jobs.push([data[1 + 2 * i], data[2 + 2 * i]]);jobs.sort((x, y) => x[1] - y[1]); // 마감이 이른 것부터 const chosen = new MaxHeap(); // 고른 주문의 시간들let total = 0; // 고른 주문을 마감 순으로 이어 뽑으면 마지막이 끝나는 시각for (const [t, d] of jobs) {  chosen.push(t);  total += t;  if (total > d) total -= chosen.pop(); // 마감을 넘으면 고른 것 가운데 가장 긴 주문을 뺀다}console.log(`${chosen.size} ${total}`);
JavaMain.java · 30 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.Arrays;import java.util.Collections;import java.util.PriorityQueue;import java.util.StringTokenizer; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(in.readLine().trim());        long[][] jobs = new long[n][]; // {걸리는 시간, 마감}. 마감은 int를 넘는다        for (int i = 0; i < n; i++) {            StringTokenizer st = new StringTokenizer(in.readLine());            jobs[i] = new long[] {Long.parseLong(st.nextToken()), Long.parseLong(st.nextToken())};        }        Arrays.sort(jobs, (x, y) -> Long.compare(x[1], y[1])); // 마감이 이른 것부터         // PriorityQueue는 기본이 최소 힙이라 reverseOrder()로 가장 긴 것이 먼저 나오게 한다        PriorityQueue<Long> chosen = new PriorityQueue<>(Collections.reverseOrder());        long total = 0; // 고른 주문을 마감 순으로 이어 뽑으면 마지막이 끝나는 시각        for (long[] job : jobs) {            chosen.add(job[0]);            total += job[0];            if (total > job[1]) total -= chosen.poll(); // 마감을 넘으면 가장 긴 주문을 뺀다        }        System.out.println(chosen.size() + " " + total);    }}
C#Main.cs · 28 줄
using System;using System.Collections.Generic; class Program{    static void Main()    {        int n = int.Parse(Console.ReadLine().Trim());        var jobs = new (long t, long d)[n]; // 마감은 int를 넘는다        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);            jobs[i] = (long.Parse(p[0]), long.Parse(p[1]));        }        Array.Sort(jobs, (x, y) => x.d.CompareTo(y.d)); // 마감이 이른 것부터         // PriorityQueue는 우선순위가 작은 것부터 나온다 → 우선순위를 -t로 주면 가장 긴 것이 먼저        var chosen = new PriorityQueue<long, long>();        long total = 0; // 고른 주문을 마감 순으로 이어 뽑으면 마지막이 끝나는 시각        foreach (var (t, d) in jobs)        {            chosen.Enqueue(t, -t);            total += t;            if (total > d) total -= chosen.Dequeue(); // 마감을 넘으면 가장 긴 주문을 뺀다        }        Console.WriteLine($"{chosen.Count} {total}");    }}
C++main.cpp · 27 줄
#include <algorithm>#include <iostream>#include <queue>#include <utility>#include <vector> int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int n;    std::cin >> n;    std::vector<std::pair<long long, long long>> jobs(n); // (마감, 걸리는 시간). 마감은 int를 넘는다    for (auto& [d, t] : jobs) std::cin >> t >> d;    std::sort(jobs.begin(), jobs.end()); // 마감이 이른 것부터     std::priority_queue<long long> chosen; // 기본이 최대 힙: top()이 가장 긴 주문    long long total = 0; // 고른 주문을 마감 순으로 이어 뽑으면 마지막이 끝나는 시각    for (auto [d, t] : jobs) {        chosen.push(t);        total += t;        if (total > d) { // 마감을 넘으면 가장 긴 주문을 뺀다            total -= chosen.top();            chosen.pop();        }    }    std::cout << chosen.size() << " " << total << "\n";}
Cmain.c · 63 줄
#include <stdio.h>#include <stdlib.h> #define MAXN 10000 typedef struct {    long long t, d; /* 걸리는 시간, 마감(int를 넘는다) */} Job; static Job jobs[MAXN];static long long heap[MAXN]; /* 최대 힙: heap[0]이 가장 크다 */static int heap_size = 0; static int by_deadline(const void* x, const void* y) {    long long a = ((const Job*)x)->d, b = ((const Job*)y)->d;    return (a > b) - (a < b); /* 빼면 int를 넘을 수 있어 비교로 */} static void swap(long long* x, long long* y) {    long long t = *x;    *x = *y;    *y = t;} static void heap_push(long long x) {    int i = heap_size++;    heap[i] = x;    while (i > 0 && heap[(i - 1) / 2] < heap[i]) {        swap(&heap[(i - 1) / 2], &heap[i]);        i = (i - 1) / 2;    }} static long long heap_pop(void) {    long long top = heap[0];    heap[0] = heap[--heap_size];    int i = 0;    for (;;) {        int l = 2 * i + 1, r = l + 1, big = i;        if (l < heap_size && heap[l] > heap[big]) big = l;        if (r < heap_size && heap[r] > heap[big]) big = r;        if (big == i) break;        swap(&heap[big], &heap[i]);        i = big;    }    return top;} int main(void) {    int n;    scanf("%d", &n);    for (int i = 0; i < n; i++) scanf("%lld %lld", &jobs[i].t, &jobs[i].d);    qsort(jobs, n, sizeof(Job), by_deadline); /* 마감이 이른 것부터 */     long long total = 0; /* 고른 주문을 마감 순으로 이어 뽑으면 마지막이 끝나는 시각 */    for (int i = 0; i < n; i++) {        heap_push(jobs[i].t);        total += jobs[i].t;        if (total > jobs[i].d) total -= heap_pop(); /* 마감을 넘으면 가장 긴 주문을 뺀다 */    }    printf("%d %lld\n", heap_size, total);    return 0;}
Craykonmain.cray · 76 줄
// 최대 힙(목록 heap): heap[0]이 가장 크고, i의 자식은 2i+1, 2i+2힙넣기(heap, x){    heap.넣기(x);    i = heap.개수 - 1;    동안 (i > 0)    {        부모 = 수학.몫(i - 1, 2);        만약 (heap[부모] >= heap[i])        {            그만;        }        t = heap[부모];        heap[부모] = heap[i];        heap[i] = t;        i = 부모;    }} 힙꺼내기(heap){    top = heap[0];    heap[0] = heap[heap.개수 - 1];    heap.자리빼기(heap.개수 - 1);    i = 0;    동안 (참)    {        l = 2 * i + 1;        r = l + 1;        big = i;        만약 (l < heap.개수 && heap[l] > heap[big])        {            big = l;        }        만약 (r < heap.개수 && heap[r] > heap[big])        {            big = r;        }        만약 (big == i)        {            그만;        }        t = heap[big];        heap[big] = heap[i];        heap[i] = t;        i = big;    }    돌려주기 top;} // 정렬 기준: 마감이 이른 주문이 앞byDeadline(x, y){    돌려주기 x[1] - y[1];} n = 콘솔.수들읽기()[0];jobs = []; // [걸리는 시간, 마감]반복 (i = 0; i < n; i++){    jobs.넣기(콘솔.수들읽기());}jobs.정렬(byDeadline); chosen = []; // 고른 주문의 시간들(최대 힙)total = 0; // 고른 주문을 마감 순으로 이어 뽑으면 마지막이 끝나는 시각각각 (job 에서 jobs){    힙넣기(chosen, job[0]);    total += job[0];    만약 (total > job[1])    {        total -= 힙꺼내기(chosen); // 마감을 넘으면 고른 것 가운데 가장 긴 주문을 뺀다    }}콘솔.쓰기(chosen.개수 + " " + total);