一台 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
错误列表00F8 ↓ · ⇧F8 ↑
输入(stdin)
运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。
$ 就绪
请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。
其他语言怎么写?查看各语言题解 · 8 种语言题解
挑出的订单最好按截止时刻从早到晚连续打印。 所以把订单按截止时刻排序后逐个加入,当累计时间超过这一单的截止时刻时,就从已挑的订单里去掉最耗时的一个(贪心——数量少一个,但剩下的时间减得最多)。 要快速取出最长的,需要最大堆。 这样剩下的就是数量最多、并且其中时间总和最小的挑法。 陷阱一:放不下的订单直接跳过是错的——第一个例子里先放进 4 小时的订单,就只能再放两个 1 小时的,共 3 个;去掉 4 小时的则是 4 个。 陷阱二:从短订单开始放,截止晚的短订单会抢走截止早的订单的位置。 陷阱三:时间总和可达 100 亿,截止时刻也超过 int,所以 C、C++ 要用 long long,Java、C# 要用 long(Java、C# 的 int 超过约 21 亿就会绕成负数。 Python、JavaScript、TypeScript、Craykon 直接就行——远小于 2^53)。 陷阱四:得到最大堆的方法在各语言中正好相反。 C++ 的 std::priority_queue默认就是最大堆,直接用;Java 的 PriorityQueue 默认是最小堆,要传 Collections.reverseOrder()(不传就会去掉最短的订单)。 Python heapq 只有最小堆,所以放入 -t,取出后再把符号变回来;C# PriorityQueue<long, long> 把优先级设为 -t。 JavaScript、TypeScript、C、Craykon 用列表自己写堆,把比较方向反过来,让父节点大于等于子节点(父节点位置在 Craykon 中是 数学.商(i - 1, 2))。 排序规则的写法也不同:C 的 qsort 比较函数返回 int,所以不要把 long long 截止时刻相减,而是用 (a > b) - (a < b);Craykon 给 jobs.排序(byDeadline) 传一个返回 x[1] - y[1] 的函数(数是双精度,相减没问题)。
Pythonmain.py · 17 行
import heapqimport sysdata = 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 10000typedef 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);