{ }codelper
二分查找倒序循环P0240

会议室什么时候空

给出一间会议室一天的 n 个预约和 q 个问题。
时刻是非负整数。
第一行给出 n 和 q。
接下来 n 行每行以 "s e" 的形式给出一个预约。
这个预约从时刻 s 开始使用会议室,直到 e 之前。
所以时刻 s 在使用中,时刻 e 不再被这个预约使用。
预约按开始时刻从早到晚给出,互不重叠。
但前一个预约结束的时刻,下一个预约可以立刻开始。
这样的预约称为相接。
最后一行给出 q 个要询问的时刻 t,用空格分开。
对每个问题输出一行答案。
如果时刻 t 有预约在使用会议室,输出 "busy X"。
X 是这个预约以及之后相接的预约全部结束、会议室第一次空出来的时刻。
如果时刻 t 没有预约在使用,输出 "free Y"。
Y 是在 t 之后开始的预约中最早那个的开始时刻。
如果 t 之后没有开始的预约,输出 "free none"。
预约和问题都很多,不要每个问题都从头扫描预约,要用对半查找。

输入
1 ≤ n ≤ 10,000,1 ≤ q ≤ 10,000,0 ≤ s < e ≤ 1,000,000,000,前一个预约的 e ≤ 下一个预约的 s,0 ≤ t ≤ 1,000,000,000
输出
每个问题一行:busy X、free Y、free none 之一
样例 1 · stdin
3 6
9 10
10 12
14 16
9 11 12 13 16 3
stdout
busy 12
busy 12
free 14
free 14
free none
free 9
main.py
输入(stdin)

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

$ 就绪

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

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

在有序数组里查找的工具各语言不同。
Python bisect.bisect_right(S, t) 和 C++ std::upper_bound 给出第一个大于 t 的位置,它的前一个就是“开始时刻不超过 t 的最后一个预约”。
Java Arrays.binarySearch 和 C# Array.BinarySearch 找到相同的值就返回那个位置,找不到就返回负数 -(插入位置) - 1(C# 用 ~pos 取回插入位置)。
有多个相同值时不保证返回哪个位置,但这道题的开始时刻各不相同,所以可以用。
JS/TS 和 Craykon 没有这样的函数,C 的 bsearch 只能找相等的值,所以要自己写。
让 lo、hi 保持“S[0..lo) 不超过 t,S[hi..n) 大于 t”,循环结束时 lo - 1 就是答案。
Craykon 和 JS 的 / 会除出小数,所以中点用 数学.商(lo + hi, 2) 或 >> 1 求。
第一个陷阱是边界。
用 bisect_left(相同值前面的位置)的话,t 等于某个预约的开始时刻时会漏掉这个预约。
预约持续到 e 之前,t == e 时这个预约已不在使用,所以条件是 t < E[i]。
第二个陷阱是比第一个预约还早的 t。
找到的编号是 -1,而 Python 的 E[-1] 不是错误,而是最后一个预约,于是会错误地输出 busy。
Java、C# 和 Craykon 会因越界读取报错停止,JS/TS 得到 undefined,比较总是假,C/C++ 是未定义行为,所以要先判断 i >= 0。
第三,每个问题都沿着相接的预约走下去,链很长时会很慢,所以要从后往前把“从这里接下去第一次空出的时刻”一次填好。

Pythonmain.py · 25 行
import sysfrom bisect import bisect_right data = sys.stdin.read().split()n, q = int(data[0]), int(data[1])S = [int(x) for x in data[2:2 + 2 * n:2]]E = [int(x) for x in data[3:3 + 2 * n:2]]queries = data[2 + 2 * n:] # block_end[i]: i번 예약부터 맞닿은 예약을 따라가다 처음 비는 시각(뒤에서부터 채운다)block_end = E[:]for i in range(n - 2, -1, -1):    if E[i] == S[i + 1]:        block_end[i] = block_end[i + 1] out = []for t in map(int, queries):    i = bisect_right(S, t) - 1  # 시작 시각이 t 이하인 마지막 예약(없으면 -1)    if i >= 0 and t < E[i]:     # i가 -1이면 E[-1]이 마지막 예약이 되니 먼저 확인한다        out.append(f"busy {block_end[i]}")    elif i + 1 < n:        out.append(f"free {S[i + 1]}")    else:        out.append("free none")print("\n".join(out))
JavaScriptmain.js · 31 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, q] = data;const S = [], E = [];for (let i = 0; i < n; i++) {  S.push(data[2 + 2 * i]);  E.push(data[3 + 2 * i]);}// blockEnd[i]: i번 예약부터 맞닿은 예약을 따라가다 처음 비는 시각(뒤에서부터 채운다)const blockEnd = E.slice();for (let i = n - 2; i >= 0; i--) if (E[i] === S[i + 1]) blockEnd[i] = blockEnd[i + 1]; // 시작 시각이 t 이하인 마지막 예약의 번호(없으면 -1)function lastStartAtMost(t) {  let lo = 0, hi = n; // 답은 [lo, hi) 바로 앞: S[0..lo)는 t 이하, S[hi..n)은 t 초과  while (lo < hi) {    const mid = (lo + hi) >> 1;    if (S[mid] <= t) lo = mid + 1;    else hi = mid;  }  return lo - 1;} const out = [];for (let j = 0; j < q; j++) {  const t = data[2 + 2 * n + j];  const i = lastStartAtMost(t);  if (i >= 0 && t < E[i]) out.push(`busy ${blockEnd[i]}`);  else if (i + 1 < n) out.push(`free ${S[i + 1]}`);  else out.push("free none");}console.log(out.join("\n"));
TypeScriptmain.ts · 32 行
import { readFileSync } from "node:fs";const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, q] = data;const S: number[] = [], E: number[] = [];for (let i = 0; i < n; i++) {  S.push(data[2 + 2 * i]);  E.push(data[3 + 2 * i]);}// blockEnd[i]: i번 예약부터 맞닿은 예약을 따라가다 처음 비는 시각(뒤에서부터 채운다)const blockEnd: number[] = E.slice();for (let i = n - 2; i >= 0; i--) if (E[i] === S[i + 1]) blockEnd[i] = blockEnd[i + 1]; // 시작 시각이 t 이하인 마지막 예약의 번호(없으면 -1)function lastStartAtMost(t: number): number {  let lo = 0, hi = n; // S[0..lo)는 t 이하, S[hi..n)은 t 초과  while (lo < hi) {    const mid: number = (lo + hi) >> 1;    if (S[mid] <= t) lo = mid + 1;    else hi = mid;  }  return lo - 1;} const out: string[] = [];for (let j = 0; j < q; j++) {  const t: number = data[2 + 2 * n + j];  const i: number = lastStartAtMost(t);  if (i >= 0 && t < E[i]) out.push(`busy ${blockEnd[i]}`);  else if (i + 1 < n) out.push(`free ${S[i + 1]}`);  else out.push("free none");}console.log(out.join("\n"));
JavaMain.java · 46 行
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.StringTokenizer; public class Main {    static int n;    static int[] S, E;     // 시작 시각이 t 이하인 마지막 예약의 번호(없으면 -1)    static int lastStartAtMost(int t) {        int lo = 0, hi = n; // S[0..lo)는 t 이하, S[hi..n)은 t 초과        while (lo < hi) {            int mid = (lo + hi) >>> 1;            if (S[mid] <= t) lo = mid + 1;            else hi = mid;        }        return lo - 1;    }     public static void main(String[] args) throws Exception {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(in.readLine());        n = Integer.parseInt(st.nextToken());        int q = Integer.parseInt(st.nextToken());        S = new int[n];        E = new int[n];        for (int i = 0; i < n; i++) {            st = new StringTokenizer(in.readLine());            S[i] = Integer.parseInt(st.nextToken());            E[i] = Integer.parseInt(st.nextToken());        }        // blockEnd[i]: i번 예약부터 맞닿은 예약을 따라가다 처음 비는 시각(뒤에서부터 채운다)        int[] blockEnd = E.clone();        for (int i = n - 2; i >= 0; i--) if (E[i] == S[i + 1]) blockEnd[i] = blockEnd[i + 1];        st = new StringTokenizer(in.readLine());        StringBuilder out = new StringBuilder();        for (int j = 0; j < q; j++) {            int t = Integer.parseInt(st.nextToken());            int i = lastStartAtMost(t);            if (i >= 0 && t < E[i]) out.append("busy ").append(blockEnd[i]).append('\n');            else if (i + 1 < n) out.append("free ").append(S[i + 1]).append('\n');            else out.append("free none\n");        }        System.out.print(out);    }}
C#Main.cs · 35 行
using System;using System.Text; class Program{    static void Main()    {        string[] first = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(first[0]), q = int.Parse(first[1]);        int[] S = new int[n], E = new int[n];        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);            S[i] = int.Parse(p[0]);            E[i] = int.Parse(p[1]);        }        // blockEnd[i]: i번 예약부터 맞닿은 예약을 따라가다 처음 비는 시각(뒤에서부터 채운다)        int[] blockEnd = (int[])E.Clone();        for (int i = n - 2; i >= 0; i--) if (E[i] == S[i + 1]) blockEnd[i] = blockEnd[i + 1];        string[] qs = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        var sb = new StringBuilder();        for (int j = 0; j < q; j++)        {            int t = int.Parse(qs[j]);            // Array.BinarySearch는 같은 값이 없으면 ~(t보다 큰 첫 자리)를 돌려준다.            // 시작 시각은 모두 다르니, 찾으면 그 자리, 못 찾으면 그 앞 자리가 "t 이하인 마지막"이다            int pos = Array.BinarySearch(S, t);            int i = pos >= 0 ? pos : ~pos - 1;            if (i >= 0 && t < E[i]) sb.Append("busy ").Append(blockEnd[i]).Append('\n');            else if (i + 1 < n) sb.Append("free ").Append(S[i + 1]).Append('\n');            else sb.Append("free none\n");        }        Console.Write(sb);    }}
C++main.cpp · 27 行
#include <algorithm>#include <iostream>#include <string>#include <vector> int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int n, q;    std::cin >> n >> q;    std::vector<int> S(n), E(n);    for (int i = 0; i < n; i++) std::cin >> S[i] >> E[i];    // blockEnd[i]: i번 예약부터 맞닿은 예약을 따라가다 처음 비는 시각(뒤에서부터 채운다)    std::vector<int> blockEnd = E;    for (int i = n - 2; i >= 0; i--) if (E[i] == S[i + 1]) blockEnd[i] = blockEnd[i + 1];    std::string out;    for (int j = 0; j < q; j++) {        int t;        std::cin >> t;        // upper_bound는 t보다 큰 첫 자리. 그 바로 앞이 시작 시각이 t 이하인 마지막 예약이다        int i = int(std::upper_bound(S.begin(), S.end(), t) - S.begin()) - 1;        if (i >= 0 && t < E[i]) out += "busy " + std::to_string(blockEnd[i]) + "\n";        else if (i + 1 < n) out += "free " + std::to_string(S[i + 1]) + "\n";        else out += "free none\n";    }    std::cout << out;}
Cmain.c · 33 行
#include <stdio.h> static int S[10000], E[10000], blockEnd[10000];static int n; /* 시작 시각이 t 이하인 마지막 예약의 번호(없으면 -1) */int last_start_at_most(int t) {    int lo = 0, hi = n; /* S[0..lo)는 t 이하, S[hi..n)은 t 초과 */    while (lo < hi) {        int mid = lo + (hi - lo) / 2;        if (S[mid] <= t) lo = mid + 1;        else hi = mid;    }    return lo - 1;} int main(void) {    int q, t;    scanf("%d %d", &n, &q);    for (int i = 0; i < n; i++) scanf("%d %d", &S[i], &E[i]);    /* blockEnd[i]: i번 예약부터 맞닿은 예약을 따라가다 처음 비는 시각(뒤에서부터 채운다) */    for (int i = n - 1; i >= 0; i--) {        blockEnd[i] = (i + 1 < n && E[i] == S[i + 1]) ? blockEnd[i + 1] : E[i];    }    for (int j = 0; j < q; j++) {        scanf("%d", &t);        int i = last_start_at_most(t);        if (i >= 0 && t < E[i]) printf("busy %d\n", blockEnd[i]);        else if (i + 1 < n) printf("free %d\n", S[i + 1]);        else puts("free none");    }    return 0;}
Craykonmain.cray · 58 行
첫줄 = 控制台.读数字();n = 첫줄[0];q = 첫줄[1];S = [];E = [];循环 (i = 0; i < n; i++){    p = 控制台.读数字();    S.添加(p[0]);    E.添加(p[1]);}// blockEnd[i]: i번 예약부터 맞닿은 예약을 따라가다 처음 비는 시각(뒤에서부터 채운다)blockEnd = E.子列表(0, n);循环 (i = n - 2; i >= 0; i--){    如果 (E[i] == S[i + 1])    {        blockEnd[i] = blockEnd[i + 1];    }} // 시작 시각이 t 이하인 마지막 예약의 번호(없으면 -1)마지막시작(t){    lo = 0;    hi = n; // S[0..lo)는 t 이하, S[hi..n)은 t 초과    当 (lo < hi)    {        mid = 数学.商(lo + hi, 2); // /는 소수까지 나누니 몫을 쓴다        如果 (S[mid] <= t)        {            lo = mid + 1;        }        否则        {            hi = mid;        }    }    返回 lo - 1;} qs = 控制台.读数字();遍历 (t 在 qs){    i = 마지막시작(t);    如果 (i >= 0 && t < E[i])    {        控制台.输出("busy " + blockEnd[i]);    }    否则 如果 (i + 1 < n)    {        控制台.输出("free " + S[i + 1]);    }    否则    {        控制台.输出("free none");    }}