{ }codelper
優先度付きキュー(ヒープ)64ビット整数P0294

いちばん走っていない自転車から貸す

貸し自転車屋に自転車が n 台あり、1 番から n 番まで番号が付いています。
i 番の自転車はこれまでに wᵢ だけ走っています。
客が q 人、順に来ます。
客は一人ずつ来て、前の客が自転車を返してから次の客が来ます。
店は客が来るたびに、残っている自転車のうち走った距離がいちばん短い自転車を貸します。
走った距離が同じ自転車が複数あれば、番号がいちばん小さい自転車を貸します。
j 番目の客は dⱼ だけ乗って返し、その自転車の走った距離は dⱼ だけ増えます。
返ってきた自転車の走った距離が L 以上なら、その自転車は整備工場に送り、二度と貸しません。
客が来たときに残っている自転車が 1 台もなければ、その客は自転車を借りられません。
客ごとに 1 行に、借りた自転車の番号を出力してください。
借りられなかった客は 0 を出力します。
最後の行に、整備工場に送った自転車の数を出力してください。

入力
1 行目に n, q, L が空白一つで区切られて与えられます。
2 行目に w₁ … wₙ、3 行目に 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。
すべて整数です
出力
客ごとに 1 行ずつ q 行に自転車の番号(借りられなければ 0)、続けて 1 行に整備工場に送った自転車の数
例 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 回ですが、ヒープなら 1 回あたり 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 を使います。
落とし穴三:距離は 1 兆を超えることがあるので、C・C++ は long long、Java・C# は long が必要です。
int に入れると値が壊れます。
Python の整数には上限がなく、JavaScript・TypeScript・Craykon の数は 2^53(約 9000 兆)まで正確なので、そのまま使えます。
落とし穴四: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);