{ }codelper
优先队列(堆)64 位整数P0294

先借出骑得最少的自行车

自行车租赁点有 n 辆自行车,编号为 1 到 n。
i 号自行车到现在已经骑了 wᵢ 的距离。
有 q 位顾客依次到来。
顾客一位一位地来,前一位顾客还车之后下一位才来。
每来一位顾客,租赁点就把剩下的自行车中已骑距离最短的那辆借出去。
如果有多辆自行车已骑距离相同,就借出编号最小的那辆。
第 j 位顾客骑 dⱼ 后归还,这辆自行车的已骑距离增加 dⱼ。
如果归还的自行车已骑距离大于等于 L,就把它送去维修,以后不再借出。
顾客到来时如果一辆自行车都不剩,这位顾客就借不到车。
对每位顾客输出一行:他借走的自行车的编号。
借不到车的顾客输出 0。
最后一行输出送去维修的自行车数量。

输入
第一行是 n、q、L,用一个空格隔开。
第二行是 w₁ … wₙ,第三行是 d₁ … d_q,用一个空格隔开。
1 ≤ n ≤ 10,000,1 ≤ q ≤ 10,000,1 ≤ L ≤ 1,000,000,000,000,0 ≤ wᵢ < L,1 ≤ dⱼ ≤ 1,000,000,000。
全部是整数
输出
每位顾客一行,共 q 行自行车编号(借不到则为 0),然后一行是送去维修的自行车数量
样例 1 · stdin
3 7 10
4 2 2
3 3 5 4 2 6 1
stdout
2
3
1
2
3
3
1
2
样例 2 · stdin
1 3 5
0
5 1 1
stdout
1
0
0
1
main.py
输入(stdin)

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

$ 就绪

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

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

反复取出最小的、改变它的值再放回去时,用的就是堆(优先队列)。
每来一位顾客都把自行车全看一遍需要 n × q 次,而堆每次只要大约 log n 次。
第一个例子中第四位顾客来时 2 号和 3 号都是 5,所以借出 2 号;第七位顾客骑的 1 号变成 9 + 1 = 10,正好等于 L,被送去维修。
陷阱一:距离相同时取编号小的自行车。
堆不会保持相同值之间的顺序,所以要把编号也放进去一起比较。
Python 往 heapq 里放 (距离, 编号) 元组,C++ 往 priority_queue 里放 pair,都从前面开始比较。
C++ 的 priority_queue 默认大的先出,传入 greater 才是小的先出。
C# 像 PriorityQueue<int, (long, int)> 这样用元组作优先级。
陷阱二:把 Java PriorityQueue 的比较函数写成 (int) (a[0] - b[0]) 这样的减法是错的。
距离之差超过约 21 亿时,截成 int 符号会反过来。
要用 Long.compare。
陷阱三:距离可能超过一万亿,所以 C、C++ 需要 long long,Java、C# 需要 long。
放进 int 值就坏了。
Python 的整数没有上限,JavaScript、TypeScript 和 Craykon 的数在 2^53(约九千万亿)以内是精确的,可以直接用。
陷阱四:是大于等于 L。
正好等于 L 的自行车也要送去维修。
陷阱五:开始时只排序一次,然后按那个顺序轮流借出是错的。
骑过之后距离变了,顺序也变了。
陷阱六:自行车可能借光。
从空堆取出之前要先看是否为空。
Python 的 heappop 会抛出 IndexError,Java 的 poll 返回 null,C++ 的 top 是未定义行为,C# 的 TryDequeue 返回 false。
JavaScript、TypeScript、C 和 Craykon 没有堆,所以用数组自己实现。
这些解法在堆里只放编号,比较时查看距离数组。
自行车在堆里时不改它的距离,取出之后再改距离并重新放入,所以堆不会乱。
Craykon 用 添加 加入列表后向上调整,用 按位置移除 取下最后一个元素放到最上面再向下调整。

Pythonmain.py · 26 行
import heapqimport sys data = sys.stdin.read().split()n, q, limit = int(data[0]), int(data[1]), int(data[2]) # 힙에 (달린 거리, 번호) 튜플을 넣는다: 튜플은 앞에서부터 견주니 거리가 같으면 번호가 작은 것이 위로 온다heap = [(int(data[3 + i]), i + 1) for i in range(n)]heapq.heapify(heap) out = []repaired = 0for j in range(q):    d = int(data[3 + n + j])    if not heap:  # 빌려줄 자전거가 없다        out.append("0")        continue    dist, bike = heapq.heappop(heap)    out.append(str(bike))    dist += d    if dist >= limit:  # 딱 L이 되어도 정비소로 간다        repaired += 1    else:        heapq.heappush(heap, (dist, bike))out.append(str(repaired))print("\n".join(out))
JavaScriptmain.js · 58 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, q, limit] = data; // JavaScript에는 힙이 없어서 직접 만든다. dist[b] = b번 자전거가 달린 거리, heap에는 번호만 넣는다const dist = [0, ...data.slice(3, 3 + n)];// a번이 b번보다 먼저 나가야 하는가: 거리가 짧은 쪽, 같으면 번호가 작은 쪽const before = (a, b) => dist[a] < dist[b] || (dist[a] === dist[b] && a < b);const heap = []; function push(b) {  heap.push(b);  let i = heap.length - 1;  while (i > 0) {    const parent = (i - 1) >> 1;    if (!before(heap[i], heap[parent])) break;    [heap[i], heap[parent]] = [heap[parent], heap[i]];    i = parent;  }} function pop() {  const top = heap[0];  const last = heap.pop();  if (heap.length > 0) {    heap[0] = last;    let i = 0;    for (;;) {      const l = 2 * i + 1;      const r = l + 1;      let first = i;      if (l < heap.length && before(heap[l], heap[first])) first = l;      if (r < heap.length && before(heap[r], heap[first])) first = r;      if (first === i) break;      [heap[i], heap[first]] = [heap[first], heap[i]];      i = first;    }  }  return top;} for (let b = 1; b <= n; b++) push(b); const out = [];let repaired = 0;for (let j = 0; j < q; j++) {  const d = data[3 + n + j];  if (heap.length === 0) {    out.push(0); // 빌려줄 자전거가 없다    continue;  }  const bike = pop();  out.push(bike);  dist[bike] += d; // 10^12 남짓이라 number로도 정확하다  if (dist[bike] >= limit) repaired++; // 딱 L이 되어도 정비소로 간다  else push(bike);}out.push(repaired);console.log(out.join("\n"));
TypeScriptmain.ts · 60 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, q, limit] = data; // TypeScript에도 힙이 없어서 직접 만든다. dist[b] = b번 자전거가 달린 거리, heap에는 번호만 넣는다const dist: number[] = [0, ...data.slice(3, 3 + n)];// a번이 b번보다 먼저 나가야 하는가: 거리가 짧은 쪽, 같으면 번호가 작은 쪽const before = (a: number, b: number): boolean => dist[a] < dist[b] || (dist[a] === dist[b] && a < b);const heap: number[] = []; function push(b: number): void {  heap.push(b);  let i = heap.length - 1;  while (i > 0) {    const parent = (i - 1) >> 1;    if (!before(heap[i], heap[parent])) break;    [heap[i], heap[parent]] = [heap[parent], heap[i]];    i = parent;  }} function pop(): number {  const top = heap[0];  const last = heap.pop() as number;  if (heap.length > 0) {    heap[0] = last;    let i = 0;    for (;;) {      const l = 2 * i + 1;      const r = l + 1;      let first = i;      if (l < heap.length && before(heap[l], heap[first])) first = l;      if (r < heap.length && before(heap[r], heap[first])) first = r;      if (first === i) break;      [heap[i], heap[first]] = [heap[first], heap[i]];      i = first;    }  }  return top;} for (let b = 1; b <= n; b++) push(b); const out: number[] = [];let repaired = 0;for (let j = 0; j < q; j++) {  const d = data[3 + n + j];  if (heap.length === 0) {    out.push(0); // 빌려줄 자전거가 없다    continue;  }  const bike = pop();  out.push(bike);  dist[bike] += d; // 10^12 남짓이라 number로도 정확하다  if (dist[bike] >= limit) repaired++; // 딱 L이 되어도 정비소로 간다  else push(bike);}out.push(repaired);console.log(out.join("\n"));
JavaMain.java · 36 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(br.readLine());        int n = Integer.parseInt(st.nextToken());        int q = Integer.parseInt(st.nextToken());        long limit = Long.parseLong(st.nextToken()); // 10^12까지라 long         // {달린 거리, 번호}를 넣는다. 거리가 짧은 것, 같으면 번호가 작은 것이 먼저 — 뺄셈이 아니라 Long.compare로 견준다        PriorityQueue<long[]> heap = new PriorityQueue<>(            (a, b) -> a[0] != b[0] ? Long.compare(a[0], b[0]) : Long.compare(a[1], b[1]));        st = new StringTokenizer(br.readLine());        for (int b = 1; b <= n; b++) heap.add(new long[] {Long.parseLong(st.nextToken()), b});         StringBuilder sb = new StringBuilder();        int repaired = 0;        st = new StringTokenizer(br.readLine());        for (int j = 0; j < q; j++) {            long d = Long.parseLong(st.nextToken());            if (heap.isEmpty()) { // 빌려줄 자전거가 없다                sb.append("0\n");                continue;            }            long[] top = heap.poll();            sb.append(top[1]).append('\n');            top[0] += d;            if (top[0] >= limit) repaired++; // 딱 L이 되어도 정비소로 간다            else heap.add(top);        }        sb.append(repaired).append('\n');        System.out.print(sb);    }}
C#Main.cs · 37 行
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]), q = int.Parse(data[1]);        long limit = long.Parse(data[2]); // 10^12까지라 long         // PriorityQueue<값, 우선순위>: 우선순위가 작은 것이 먼저 나온다        // 우선순위를 (달린 거리, 번호) 튜플로 주면 거리가 같을 때 번호가 작은 것이 먼저다        var heap = new PriorityQueue<int, (long, int)>();        for (int b = 1; b <= n; b++) heap.Enqueue(b, (long.Parse(data[2 + b]), b));         var sb = new StringBuilder();        int repaired = 0;        for (int j = 0; j < q; j++)        {            long d = long.Parse(data[3 + n + j]);            // TryDequeue는 비어 있으면 false를 돌려준다: 빌려줄 자전거가 없다            if (!heap.TryDequeue(out int bike, out (long dist, int id) key))            {                sb.Append("0\n");                continue;            }            sb.Append(bike).Append('\n');            long dist = key.dist + d;            if (dist >= limit) repaired++; // 딱 L이 되어도 정비소로 간다            else heap.Enqueue(bike, (dist, bike));        }        sb.Append(repaired).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 42 行
#include <functional>#include <iostream>#include <queue>#include <utility>#include <vector>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, q;    long long limit; // 10^12까지라 long long    cin >> n >> q >> limit;     // priority_queue는 기본이 큰 것 먼저다: greater를 주어 작은 것이 먼저 나오게 한다    // pair는 first부터 견주니 (달린 거리, 번호)로 넣으면 거리가 같을 때 번호가 작은 것이 먼저다    typedef pair<long long, int> Bike;    priority_queue<Bike, vector<Bike>, greater<Bike>> heap;    for (int b = 1; b <= n; b++) {        long long w;        cin >> w;        heap.push({w, b});    }     int repaired = 0;    for (int j = 0; j < q; j++) {        long long d;        cin >> d;        if (heap.empty()) { // 빌려줄 자전거가 없다            cout << "0\n";            continue;        }        Bike top = heap.top();        heap.pop();        cout << top.second << '\n';        top.first += d;        if (top.first >= limit) repaired++; // 딱 L이 되어도 정비소로 간다        else heap.push(top);    }    cout << repaired << '\n';    return 0;}
Cmain.c · 69 行
#include <stdio.h>#include <stdlib.h> // C에는 힙이 없어서 직접 만든다. dist[b] = b번 자전거가 달린 거리, heap에는 번호만 넣는다static long long *dist;static int *heap;static int size = 0; // a번이 b번보다 먼저 나가야 하는가: 거리가 짧은 쪽, 같으면 번호가 작은 쪽static int before(int a, int b) {    return dist[a] < dist[b] || (dist[a] == dist[b] && a < b);} static void push(int b) {    int i = size++;    heap[i] = b;    while (i > 0) {        int parent = (i - 1) / 2;        if (!before(heap[i], heap[parent])) break;        int t = heap[i]; heap[i] = heap[parent]; heap[parent] = t;        i = parent;    }} static int pop(void) {    int top = heap[0];    heap[0] = heap[--size];    int i = 0;    for (;;) {        int l = 2 * i + 1, r = l + 1, first = i;        if (l < size && before(heap[l], heap[first])) first = l;        if (r < size && before(heap[r], heap[first])) first = r;        if (first == i) break;        int t = heap[i]; heap[i] = heap[first]; heap[first] = t;        i = first;    }    return top;} int main(void) {    int n, q;    long long limit; // 10^12까지라 int에는 안 든다    if (scanf("%d %d %lld", &n, &q, &limit) != 3) return 0;    dist = malloc((n + 1) * sizeof(long long));    heap = malloc((n + 1) * sizeof(int));    for (int b = 1; b <= n; b++) {        scanf("%lld", &dist[b]);        push(b);    }     int repaired = 0;    for (int j = 0; j < q; j++) {        long long d;        scanf("%lld", &d);        if (size == 0) { // 빌려줄 자전거가 없다            printf("0\n");            continue;        }        int bike = pop();        printf("%d\n", bike);        dist[bike] += d;        if (dist[bike] >= limit) repaired++; // 딱 L이 되어도 정비소로 간다        else push(bike);    }    printf("%d\n", repaired);    free(dist);    free(heap);    return 0;}
Craykonmain.cray · 94 行
// 크레이콘에는 힙이 없어서 직접 만든다. dist[b] = b번 자전거가 달린 거리, heap에는 번호만 넣는다(번호는 0부터)첫줄 = 控制台.读数字();n = 첫줄[0];q = 첫줄[1];limit = 첫줄[2];dist = 控制台.读数字();타는거리 = 控制台.读数字();heap = []; // a번이 b번보다 먼저 나가야 하는가: 거리가 짧은 쪽, 같으면 번호가 작은 쪽먼저인가(a, b){    返回 dist[a] < dist[b] || dist[a] == dist[b] && a < b;} 힙넣기(b){    heap.添加(b);    i = heap.数量 - 1;    当 (i > 0)    {        부모 = 数学.商(i - 1, 2);        如果 (!먼저인가(heap[i], heap[부모]))        {            跳出;        }        t = heap[i];        heap[i] = heap[부모];        heap[부모] = t;        i = 부모;    }} 힙빼기(){    top = heap[0];    heap[0] = heap[heap.数量 - 1];    heap.按位置移除(heap.数量 - 1);    i = 0;    当 (真)    {        l = 2 * i + 1;        r = l + 1;        first = i;        如果 (l < heap.数量 && 먼저인가(heap[l], heap[first]))        {            first = l;        }        如果 (r < heap.数量 && 먼저인가(heap[r], heap[first]))        {            first = r;        }        如果 (first == i)        {            跳出;        }        t = heap[i];        heap[i] = heap[first];        heap[first] = t;        i = first;    }    返回 top;} 循环 (b = 0; b < n; b++){    힙넣기(b);} out = [];repaired = 0;遍历 (d 在 타는거리){    如果 (heap.数量 == 0)    {        out.添加(0); // 빌려줄 자전거가 없다    }    否则    {        bike = 힙빼기();        out.添加(bike + 1);        dist[bike] += d;        如果 (dist[bike] >= limit)        {            repaired += 1;        }        否则        {            힙넣기(bike);        }    }}控制台.输出(out.连接("\n"));控制台.输出(repaired);