{ }codelper
优先队列(堆)模拟64 位整数P0277

越等越急的看诊顺序

急诊室有 k 间诊室,编号 1 到 k。
按到达顺序给出 n 名患者,编号 1 到 n。
i 号患者在第 aᵢ 分钟到达并排队,初始紧急度为 sᵢ,看诊需要 dᵢ 分钟。
在队里等待的患者,紧急度每分钟增加 1。
也就是说,第 T 分钟时 i 号患者的紧急度为 sᵢ + (T − aᵢ)。
在同一时刻 T,事情按以下顺序发生。
① 在第 T 分钟结束看诊的诊室全部空出。
② 在第 T 分钟到达的患者全部排进队里。
③ 只要既有空诊室又有等待的患者,空诊室中编号最小的那间就叫等待的患者中第 T 分钟最紧急的那位。
紧急度相同时叫编号较小的人。
被叫到的患者在第 T 分钟开始看诊,在第 T + dᵢ 分钟结束,这期间该诊室不叫别的患者。
一开始(第 0 分钟之前)所有诊室都是空的。
第一行按被叫到的顺序输出患者编号。
第二行输出所有患者等待时间(开始看诊的时刻 − 到达时刻)之和。
第三行依次输出 1 号到 k 号诊室各看了多少名患者。

输入
第一行给出 n 和 k,接下来 n 行给出 i 号患者的“aᵢ sᵢ dᵢ”,用一个空格分隔。
1 ≤ n ≤ 10,000,1 ≤ k ≤ 100,0 ≤ a₁ ≤ a₂ ≤ … ≤ aₙ ≤ 1,000,000,1 ≤ sᵢ ≤ 1,000,000,1 ≤ dᵢ ≤ 10,000。
都是整数
输出
三行:按被叫顺序的 n 个患者编号,等待时间之和(只输出数字,不加逗号),每间诊室看诊的患者数 k 个。
同一行的数用一个空格分隔
样例 1 · stdin
5 2
0 3 4
0 5 2
1 4 3
2 6 5
2 5 1
stdout
2 1 4 3 5
8
3 2
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

紧急度随时间变化,看起来不能放进堆里,但等待的人都以同样的速度(每分钟 1)增加,所以两人的先后与 T 无关,由 sᵢ − aᵢ 决定:sᵢ + (T − aᵢ) > sⱼ + (T − aⱼ) ⇔ sᵢ − aᵢ > sⱼ − aⱼ。
于是用三个堆:等待的患者(s − a 大的先,相同则编号小的先)、空诊室(编号小的先)、忙碌的诊室(结束早的先)。
下一次有变化的时刻是“下一位患者到达”和“最早结束的看诊”中较早的那个,跳到那个时刻做 ① ② ③。
每次扫描整个队伍是 n²,对 Python、Craykon 太慢。
例:第 4 分钟 3 号(4 + 3 = 7)和 5 号(5 + 2 = 7)相同,叫编号小的 3 号。
第 7 分钟两间诊室同时空出,1 号诊室叫 5 号。
陷阱一:忘了等待时间、只看 sᵢ,5 号会比 3 号先被叫,错了。
陷阱二:紧急度相同时叫编号大的是错的。
陷阱三:按“先空出的诊室先叫”(队列)选诊室是错的 — 应该是编号小的诊室。
陷阱四:先做 ③ 再做 ②,恰好在诊室空出那一刻到达的更紧急的患者会被挤到后面。
陷阱五:10,000 人挤在一间诊室时,等待时间之和约 5×10^11,超过 32 位 int(C/C++ 用 long long,Java/C# 用 long)。
各语言的堆不同:Python heapq 先弹出最小的,所以放入 (-(s − a), 编号) 元组;Java 用 PriorityQueue 加 Comparator.comparingInt(...).thenComparingInt(...);C# 用 PriorityQueue<TElement, TPriority>,优先级是 (-(s − a), 编号) 元组(小的先出);C++ std::priority_queue 先弹出最大的,要用 std::greater 反过来;JavaScript、TypeScript、C 没有堆,要自己写。
Craykon 也要自己写堆,而且列表不能比较,所以打包成一个数,如 (1000000 − (s − a)) × 100000 + 编号 — 乘数比编号小(× 1000)时,9,999 这样的编号会和前面的位混在一起。
打包后的数最大 2×10^11,在 2^53 以内。

Pythonmain.py · 39 行
import sysimport heapq data = sys.stdin.buffer.read().split()n, k = int(data[0]), int(data[1])arrive = [0] * nsev = [0] * ndur = [0] * nfor i in range(n):    arrive[i], sev[i], dur[i] = int(data[2 + 3 * i]), int(data[3 + 3 * i]), int(data[4 + 3 * i]) # 기다리는 사람의 급한 정도는 s + (T - a)인데, 모두 1분에 1씩 함께 오르니# 두 사람의 차례는 T와 상관없이 s - a로 정해진다: 큰 것이 먼저, 같으면 번호가 작은 사람이 먼저waiting = []                  # (-(s - a), 번호)free = list(range(1, k + 1))  # 빈 진료실 번호(가장 작은 것이 맨 위)busy = []                     # (끝나는 시각, 진료실 번호)order = []seen = [0] * (k + 1)total_wait = 0nxt = 0  # 아직 오지 않은 첫 환자while len(order) < n:    # 다음에 무엇인가 바뀌는 시각: 다음 환자가 오거나 진료가 끝날 때    t = min(arrive[nxt] if nxt < n else float("inf"), busy[0][0] if busy else float("inf"))    while busy and busy[0][0] == t:          # ① 진료가 끝난 방부터 빈다        heapq.heappush(free, heapq.heappop(busy)[1])    while nxt < n and arrive[nxt] == t:      # ② 그 시각에 온 환자가 줄에 선다        heapq.heappush(waiting, (-(sev[nxt] - arrive[nxt]), nxt))        nxt += 1    while free and waiting:                  # ③ 번호가 작은 빈 방부터 가장 급한 사람을 부른다        room = heapq.heappop(free)        _, p = heapq.heappop(waiting)        order.append(p + 1)        seen[room] += 1        total_wait += t - arrive[p]        heapq.heappush(busy, (t + dur[p], room)) print(" ".join(map(str, order)))print(total_wait)print(" ".join(map(str, seen[1:])))
JavaScriptmain.js · 84 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number); // 작은 것이 맨 위에 오는 이진 힙. less(x, y)가 참이면 x가 y보다 먼저 나온다class Heap {  constructor(less) {    this.a = [];    this.less = less;  }  get size() {    return this.a.length;  }  top() {    return this.a[0];  }  push(x) {    const a = this.a;    a.push(x);    let i = a.length - 1;    while (i > 0) {      const p = (i - 1) >> 1;      if (!this.less(a[i], a[p])) break;      [a[i], a[p]] = [a[p], a[i]];      i = p;    }  }  pop() {    const a = this.a;    const top = a[0];    const last = a.pop();    if (a.length > 0) {      a[0] = last;      let i = 0;      for (;;) {        const l = 2 * i + 1;        const r = l + 1;        let m = i;        if (l < a.length && this.less(a[l], a[m])) m = l;        if (r < a.length && this.less(a[r], a[m])) m = r;        if (m === i) break;        [a[i], a[m]] = [a[m], a[i]];        i = m;      }    }    return top;  }} const [n, k] = data;const arrive = [];const sev = [];const dur = [];for (let i = 0; i < n; i++) {  arrive.push(data[2 + 3 * i]);  sev.push(data[3 + 3 * i]);  dur.push(data[4 + 3 * i]);} // 급한 정도 s + (T - a)는 모두 1분에 1씩 함께 오르니 차례는 s - a로 정해진다: 큰 것이 먼저, 같으면 번호가 작은 사람const key = (p) => sev[p] - arrive[p];const waiting = new Heap((x, y) => key(x) > key(y) || (key(x) === key(y) && x < y));const free = new Heap((x, y) => x < y); // 빈 진료실 번호const busy = new Heap((x, y) => x[0] < y[0] || (x[0] === y[0] && x[1] < y[1])); // [끝나는 시각, 진료실]for (let r = 1; r <= k; r++) free.push(r); const order = [];const seen = new Array(k + 1).fill(0);let totalWait = 0;let next = 0; // 아직 오지 않은 첫 환자while (order.length < n) {  // 다음에 무엇인가 바뀌는 시각: 다음 환자가 오거나 진료가 끝날 때  const t = Math.min(next < n ? arrive[next] : Infinity, busy.size > 0 ? busy.top()[0] : Infinity);  while (busy.size > 0 && busy.top()[0] === t) free.push(busy.pop()[1]); // ① 진료가 끝난 방부터 빈다  while (next < n && arrive[next] === t) waiting.push(next++); // ② 그 시각에 온 환자가 줄에 선다  while (free.size > 0 && waiting.size > 0) {    // ③ 번호가 작은 빈 방부터 가장 급한 사람을 부른다    const room = free.pop();    const p = waiting.pop();    order.push(p + 1);    seen[room]++;    totalWait += t - arrive[p];    busy.push([t + dur[p], room]);  }}console.log([order.join(" "), totalWait, seen.slice(1).join(" ")].join("\n"));
TypeScriptmain.ts · 85 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number); // 작은 것이 맨 위에 오는 이진 힙. less(x, y)가 참이면 x가 y보다 먼저 나온다class Heap<T> {  private readonly a: T[] = [];  constructor(private readonly less: (x: T, y: T) => boolean) {}  get size(): number {    return this.a.length;  }  top(): T {    return this.a[0];  }  push(x: T): void {    const a = this.a;    a.push(x);    let i = a.length - 1;    while (i > 0) {      const p = (i - 1) >> 1;      if (!this.less(a[i], a[p])) break;      [a[i], a[p]] = [a[p], a[i]];      i = p;    }  }  pop(): T {    const a = this.a;    const top = a[0];    const last = a.pop() as T;    if (a.length > 0) {      a[0] = last;      let i = 0;      for (;;) {        const l = 2 * i + 1;        const r = l + 1;        let m = i;        if (l < a.length && this.less(a[l], a[m])) m = l;        if (r < a.length && this.less(a[r], a[m])) m = r;        if (m === i) break;        [a[i], a[m]] = [a[m], a[i]];        i = m;      }    }    return top;  }} const [n, k] = data;const arrive: number[] = [];const sev: number[] = [];const dur: number[] = [];for (let i = 0; i < n; i++) {  arrive.push(data[2 + 3 * i]);  sev.push(data[3 + 3 * i]);  dur.push(data[4 + 3 * i]);} // 급한 정도 s + (T - a)는 모두 1분에 1씩 함께 오르니 차례는 s - a로 정해진다: 큰 것이 먼저, 같으면 번호가 작은 사람const key = (p: number): number => sev[p] - arrive[p];const waiting = new Heap<number>((x, y) => key(x) > key(y) || (key(x) === key(y) && x < y));const free = new Heap<number>((x, y) => x < y); // 빈 진료실 번호type Busy = [end: number, room: number];const busy = new Heap<Busy>((x, y) => x[0] < y[0] || (x[0] === y[0] && x[1] < y[1]));for (let r = 1; r <= k; r++) free.push(r); const order: number[] = [];const seen: number[] = new Array<number>(k + 1).fill(0);let totalWait = 0;let next = 0; // 아직 오지 않은 첫 환자while (order.length < n) {  // 다음에 무엇인가 바뀌는 시각: 다음 환자가 오거나 진료가 끝날 때  const t = Math.min(next < n ? arrive[next] : Infinity, busy.size > 0 ? busy.top()[0] : Infinity);  while (busy.size > 0 && busy.top()[0] === t) free.push(busy.pop()[1]); // ① 진료가 끝난 방부터 빈다  while (next < n && arrive[next] === t) waiting.push(next++); // ② 그 시각에 온 환자가 줄에 선다  while (free.size > 0 && waiting.size > 0) {    // ③ 번호가 작은 빈 방부터 가장 급한 사람을 부른다    const room = free.pop();    const p = waiting.pop();    order.push(p + 1);    seen[room]++;    totalWait += t - arrive[p];    busy.push([t + dur[p], room]);  }}console.log([order.join(" "), totalWait, seen.slice(1).join(" ")].join("\n"));
JavaMain.java · 52 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));        st.nextToken();        int n = (int) st.nval;        st.nextToken();        int k = (int) st.nval;        int[] arrive = new int[n], sev = new int[n], dur = new int[n];        for (int i = 0; i < n; i++) {            st.nextToken(); arrive[i] = (int) st.nval;            st.nextToken(); sev[i] = (int) st.nval;            st.nextToken(); dur[i] = (int) st.nval;        }         // 급한 정도 s + (T - a)는 모두 1분에 1씩 함께 오르니 차례는 s - a로 정해진다: 큰 것이 먼저, 같으면 번호가 작은 사람        PriorityQueue<Integer> waiting = new PriorityQueue<>(                Comparator.comparingInt((Integer p) -> -(sev[p] - arrive[p])).thenComparingInt(p -> p));        PriorityQueue<Integer> free = new PriorityQueue<>(); // 빈 진료실 번호        // {끝나는 시각, 진료실}: 시각이 이르고, 같으면 방 번호가 작은 것이 먼저        PriorityQueue<int[]> busy = new PriorityQueue<>((x, y) -> x[0] != y[0] ? Integer.compare(x[0], y[0]) : Integer.compare(x[1], y[1]));        for (int r = 1; r <= k; r++) free.add(r);         StringBuilder order = new StringBuilder();        int[] seen = new int[k + 1];        long totalWait = 0; // 5×10^11까지 가서 int로는 넘친다        int called = 0, next = 0;        while (called < n) {            // 다음에 무엇인가 바뀌는 시각: 다음 환자가 오거나 진료가 끝날 때            int t = Integer.MAX_VALUE;            if (next < n) t = arrive[next];            if (!busy.isEmpty()) t = Math.min(t, busy.peek()[0]);            while (!busy.isEmpty() && busy.peek()[0] == t) free.add(busy.poll()[1]); // ① 진료가 끝난 방부터 빈다            while (next < n && arrive[next] == t) waiting.add(next++); // ② 그 시각에 온 환자가 줄에 선다            while (!free.isEmpty() && !waiting.isEmpty()) { // ③ 번호가 작은 빈 방부터 가장 급한 사람을 부른다                int room = free.poll();                int p = waiting.poll();                if (called++ > 0) order.append(' ');                order.append(p + 1);                seen[room]++;                totalWait += t - arrive[p];                busy.add(new int[] {t + dur[p], room});            }        }        StringBuilder sb = new StringBuilder();        sb.append(order).append('\n').append(totalWait).append('\n');        for (int r = 1; r <= k; r++) sb.append(seen[r]).append(r < k ? ' ' : '\n');        System.out.print(sb);    }}
C#Main.cs · 63 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(data[0]), k = int.Parse(data[1]);        var arrive = new int[n];        var sev = new int[n];        var dur = new int[n];        for (int i = 0; i < n; i++)        {            arrive[i] = int.Parse(data[2 + 3 * i]);            sev[i] = int.Parse(data[3 + 3 * i]);            dur[i] = int.Parse(data[4 + 3 * i]);        }         // 급한 정도 s + (T - a)는 모두 1분에 1씩 함께 오르니 차례는 s - a로 정해진다: 큰 것이 먼저, 같으면 번호가 작은 사람        // PriorityQueue는 우선순위가 작은 것부터 꺼내니 (-(s - a), 번호)를 우선순위로 준다        var waiting = new PriorityQueue<int, (int, int)>();        var free = new PriorityQueue<int, int>();            // 빈 진료실 번호        var busy = new PriorityQueue<int, (int, int)>();     // 진료실, 우선순위 (끝나는 시각, 진료실)        for (int r = 1; r <= k; r++) free.Enqueue(r, r);         var order = new List<int>();        var seen = new int[k + 1];        long totalWait = 0; // 5×10^11까지 가서 int로는 넘친다        int next = 0;        while (order.Count < n)        {            // 다음에 무엇인가 바뀌는 시각: 다음 환자가 오거나 진료가 끝날 때            int t = int.MaxValue;            if (next < n) t = arrive[next];            if (busy.TryPeek(out _, out var b)) t = Math.Min(t, b.Item1);            while (busy.TryPeek(out int done, out var e) && e.Item1 == t) // ① 진료가 끝난 방부터 빈다            {                busy.Dequeue();                free.Enqueue(done, done);            }            while (next < n && arrive[next] == t) // ② 그 시각에 온 환자가 줄에 선다            {                waiting.Enqueue(next, (-(sev[next] - arrive[next]), next));                next++;            }            while (free.Count > 0 && waiting.Count > 0) // ③ 번호가 작은 빈 방부터 가장 급한 사람을 부른다            {                int room = free.Dequeue();                int p = waiting.Dequeue();                order.Add(p + 1);                seen[room]++;                totalWait += t - arrive[p];                busy.Enqueue(room, (t + dur[p], room));            }        }        var sb = new StringBuilder();        sb.Append(string.Join(' ', order)).Append('\n').Append(totalWait).Append('\n');        for (int r = 1; r <= k; r++) sb.Append(seen[r]).Append(r < k ? ' ' : '\n');        Console.Write(sb);    }}
C++main.cpp · 56 行
#include <algorithm>#include <cstdint>#include <functional>#include <iostream>#include <queue>#include <utility>#include <vector> int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int n, k;    std::cin >> n >> k;    std::vector<int> arrive(n), sev(n), dur(n);    for (int i = 0; i < n; i++) std::cin >> arrive[i] >> sev[i] >> dur[i];     // priority_queue는 큰 것이 위다: std::greater를 주면 작은 것이 위    using P = std::pair<int, int>;    using MinHeap = std::priority_queue<P, std::vector<P>, std::greater<P>>;    // 급한 정도 s + (T - a)는 모두 1분에 1씩 함께 오르니 차례는 s - a로 정해진다: 큰 것이 먼저, 같으면 번호가 작은 사람    MinHeap waiting;  // (-(s - a), 번호)    MinHeap busy;     // (끝나는 시각, 진료실)    std::priority_queue<int, std::vector<int>, std::greater<int>> freeRooms;  // 빈 진료실 번호    for (int r = 1; r <= k; r++) freeRooms.push(r);     std::vector<int> order;    std::vector<int> seen(k + 1, 0);    long long totalWait = 0;  // 5×10^11까지 가서 int로는 넘친다    int next = 0;    while (static_cast<int>(order.size()) < n) {        // 다음에 무엇인가 바뀌는 시각: 다음 환자가 오거나 진료가 끝날 때        int t = next < n ? arrive[next] : INT32_MAX;        if (!busy.empty()) t = std::min(t, busy.top().first);        while (!busy.empty() && busy.top().first == t) {  // ① 진료가 끝난 방부터 빈다            freeRooms.push(busy.top().second);            busy.pop();        }        while (next < n && arrive[next] == t) {  // ② 그 시각에 온 환자가 줄에 선다            waiting.push({-(sev[next] - arrive[next]), next});            next++;        }        while (!freeRooms.empty() && !waiting.empty()) {  // ③ 번호가 작은 빈 방부터 가장 급한 사람을 부른다            int room = freeRooms.top();            freeRooms.pop();            int p = waiting.top().second;            waiting.pop();            order.push_back(p + 1);            seen[room]++;            totalWait += t - arrive[p];            busy.push({t + dur[p], room});        }    }    for (int i = 0; i < n; i++) std::cout << order[i] << (i + 1 < n ? ' ' : '\n');    std::cout << totalWait << '\n';    for (int r = 1; r <= k; r++) std::cout << seen[r] << (r < k ? ' ' : '\n');}
Cmain.c · 78 行
#include <stdio.h> #define MAXN 10000#define MAXK 100 /* 작은 것이 맨 위에 오는 이진 힙. 비교할 값을 long long 하나로 만들어 넣는다 */typedef struct {    long long a[MAXN + MAXK];    int size;} Heap; static void push(Heap *h, long long x) {    int i = h->size++;    h->a[i] = x;    while (i > 0) {        int p = (i - 1) / 2;        if (h->a[p] <= h->a[i]) break;        long long t = h->a[p];        h->a[p] = h->a[i];        h->a[i] = t;        i = p;    }} static long long pop(Heap *h) {    long long top = h->a[0];    h->a[0] = h->a[--h->size];    int i = 0;    for (;;) {        int l = 2 * i + 1, r = l + 1, m = i;        if (l < h->size && h->a[l] < h->a[m]) m = l;        if (r < h->size && h->a[r] < h->a[m]) m = r;        if (m == i) break;        long long t = h->a[m];        h->a[m] = h->a[i];        h->a[i] = t;        i = m;    }    return top;} static int arrive[MAXN], sev[MAXN], dur[MAXN], order[MAXN], seen[MAXK + 1];static Heap waiting, freeRooms, busy; int main(void) {    int n, k;    if (scanf("%d %d", &n, &k) != 2) return 0;    for (int i = 0; i < n; i++) scanf("%d %d %d", &arrive[i], &sev[i], &dur[i]);     /* 급한 정도 s + (T - a)는 모두 1분에 1씩 함께 오르니 차례는 s - a로 정해진다: 큰 것이 먼저, 같으면 번호가 작은 사람.       s - a는 -10^6 ~ 10^6이라 (10^6 - (s - a)) * 100000 + 번호가 작을수록 먼저다 */    /* 바쁜 방은 끝나는 시각 * 1000 + 방 번호(끝나는 시각은 1.01×10^8까지) */    for (int r = 1; r <= k; r++) push(&freeRooms, r);    long long totalWait = 0; /* 5×10^11까지 가서 int로는 넘친다 */    int called = 0, next = 0;    while (called < n) {        /* 다음에 무엇인가 바뀌는 시각: 다음 환자가 오거나 진료가 끝날 때 */        long long t = next < n ? arrive[next] : -1;        if (busy.size > 0 && (t < 0 || busy.a[0] / 1000 < t)) t = busy.a[0] / 1000;        while (busy.size > 0 && busy.a[0] / 1000 == t) push(&freeRooms, pop(&busy) % 1000); /* ① 진료가 끝난 방부터 빈다 */        while (next < n && arrive[next] == t) { /* ② 그 시각에 온 환자가 줄에 선다 */            push(&waiting, (1000000LL - (sev[next] - arrive[next])) * 100000 + next);            next++;        }        while (freeRooms.size > 0 && waiting.size > 0) { /* ③ 번호가 작은 빈 방부터 가장 급한 사람을 부른다 */            int room = (int)pop(&freeRooms);            int p = (int)(pop(&waiting) % 100000);            order[called++] = p + 1;            seen[room]++;            totalWait += t - arrive[p];            push(&busy, (t + dur[p]) * 1000 + room);        }    }    for (int i = 0; i < n; i++) printf("%d%c", order[i], i + 1 < n ? ' ' : '\n');    printf("%lld\n", totalWait);    for (int r = 1; r <= k; r++) printf("%d%c", seen[r], r < k ? ' ' : '\n');    return 0;}
Craykonmain.cray · 121 行
// 작은 수가 맨 위에 오는 최소 힙(목록 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;        small = i;        如果 (l < heap.数量 && heap[l] < heap[small])        {            small = l;        }        如果 (r < heap.数量 && heap[r] < heap[small])        {            small = r;        }        如果 (small == i)        {            跳出;        }        t = heap[small];        heap[small] = heap[i];        heap[i] = t;        i = small;    }    返回 top;} 첫줄 = 控制台.读数字();n = 첫줄[0];k = 첫줄[1];arrive = [];sev = [];dur = [];循环 (i = 0; i < n; i++){    p = 控制台.读数字();    arrive.添加(p[0]);    sev.添加(p[1]);    dur.添加(p[2]);} // 급한 정도 s + (T - a)는 모두 1분에 1씩 함께 오르니 차례는 s - a로 정해진다: 큰 것이 먼저, 같으면 번호가 작은 사람// 힙에는 수 하나만 넣는다: (1000000 - (s - a)) * 100000 + 번호 — 작을수록 먼저(2×10^11까지라 정확하다)waiting = [];// 바쁜 방: 끝나는 시각 * 1000 + 방 번호(끝나는 시각은 1.01×10^8까지)busy = [];free = []; // 빈 진료실 번호循环 (r = 1; r <= k; r++){    힙넣기(free, r);} order = [];seen = 新列表(k + 1, 0);totalWait = 0;next = 0; // 아직 오지 않은 첫 환자当 (order.数量 < n){    // 다음에 무엇인가 바뀌는 시각: 다음 환자가 오거나 진료가 끝날 때    t = -1;    如果 (next < n)    {        t = arrive[next];    }    如果 (busy.数量 > 0)    {        끝 = 数学.商(busy[0], 1000);        如果 (t < 0 || 끝 < t)        {            t = 끝;        }    }    // ① 진료가 끝난 방부터 빈다    当 (busy.数量 > 0 && 数学.商(busy[0], 1000) == t)    {        힙넣기(free, 힙빼기(busy) % 1000);    }    // ② 그 시각에 온 환자가 줄에 선다    当 (next < n && arrive[next] == t)    {        힙넣기(waiting, (1000000 - (sev[next] - arrive[next])) * 100000 + next);        next += 1;    }    // ③ 번호가 작은 빈 방부터 가장 급한 사람을 부른다    当 (free.数量 > 0 && waiting.数量 > 0)    {        room = 힙빼기(free);        p = 힙빼기(waiting) % 100000;        order.添加(p + 1);        seen[room] += 1;        totalWait += t - arrive[p];        힙넣기(busy, (t + dur[p]) * 1000 + room);    }}控制台.输出(order.连接(" "));控制台.输出(totalWait);seen.按位置移除(0);控制台.输出(seen.连接(" "));