走廊里带编号的储物柜按编号顺序排成一排。 一开始没有任何储物柜在使用。 n 名学生依次到来,每人选一个储物柜编号 x。 如果 x 已经在使用,输出 "已占用",并且什么都不改变。 否则,在此前已使用的储物柜中找出左右邻居的编号 L 和 R,用一个空格隔开输出在一行,然后把 x 变成已使用的储物柜。 L 是已使用的储物柜中编号小于 x 的最大编号。 R 是已使用的储物柜中编号大于 x 的最小编号。 没有这样的储物柜时,在那个位置写 -(一个减号)代替数字。 最后一行输出已使用的储物柜数。 如果你的语言的标准库里有始终保持有序的容器,就用它试试。
输入
第一行给出 n(1 ≤ n ≤ 2,000),接下来 n 行每行给出一个 x(1 ≤ x ≤ 1,000,000,000)。 编号只用数字字符书写,不带逗号。
输出
n + 1 行。 前 n 行是每名学生的 "已占用" 或 "L R",最后一行是已使用的储物柜数
样例 1 · stdin
8
50
20
80
50
9
100
35
20
stdout
- -
- 50
50 -
已占用
- 20
80 -
20 50
已占用
6
main.py
错误列表00F8 ↓ · ⇧F8 ↑
输入(stdin)
运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。
$ 就绪
请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。
其他语言怎么写?查看各语言题解 · 8 种语言题解
这道题把标准库里有“始终有序的集合”的语言和没有的语言分开了。 Java TreeSet 的 lower(x)(小于 x 的最大元素)和 higher(x) 就是答案,没有时返回 null。 C++ std::set 的 lower_bound(x) 给出第一个不小于 x 的位置,它就是右邻居,std::prev(it) 是左邻居。 C# SortedSet<int> 没有取前一个、后一个值的方法,要用 GetViewBetween(下界, 上界) 截取区间再看 Max、Min。 Python 没有有序集合,用有序的 list 加 bisect 模块(bisect_left 找位置,insert 或 insort 插入)。 JavaScript·TypeScript 的 Set 和 Craykon 的列表只保持插入顺序,不会排序,所以要逐个扫描,自己找“小于 x 的最大值”。 C 根本没有容器,就把编号按到来的顺序放进数组,同样逐个扫描。 这里 n 很小,扫描也来得及,但有序集合每次查询只需要大约 log n 步。 六个陷阱:① Java floor(x)、ceiling(x) 是“等于或”小于、大于的值,先 add 再问就会得到 x 自己。 ② C++ 中 it 等于 begin() 时没有左邻居。 只判断集合是否为空,在最小的编号到来时会出错;对 begin() 做 std::prev 是未定义行为。 ③ Python 中位置 i 为 0 时,used[i - 1] 不会报错,而是最后一个值 used[-1]。 ④ C# 中空的 SortedSet<int> 的 Max、Min 不报错而返回 0。 先看 Count。 ⑤ JS 数组的 sort() 没有比较函数时会把数转成字符串按字典序排,10 排在 9 前面。 要写 sort((a, b) => a - b)。 ⑥ 列表和集合不同,会再次接受相同的值。 不自己判断是否已存在,就会漏掉 "taken",个数也会变多(Java 的 add 在元素已存在时返回 false)。
Pythonmain.py · 15 行
from bisect import bisect_leftn = int(input())used = [] # 쓰는 사물함 번호를 늘 작은 것부터 둔다for _ in range(n): x = int(input()) i = bisect_left(used, x) # x가 들어갈 자리: 그 왼쪽은 모두 x보다 작다 if i < len(used) and used[i] == x: print("已占用") continue left = used[i - 1] if i > 0 else "-" # i가 0일 때 used[-1]은 맨 끝 값이라 따로 본다 right = used[i] if i < len(used) else "-" print(left, right) used.insert(i, x)print(len(used))
JavaScriptmain.js · 22 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);// JavaScript에는 정렬된 집합이 없어서, 쓰는 번호를 Set에 두고 양옆 이웃은 하나씩 훑어 찾는다const used = new Set();const out = [];for (let i = 1; i <= n; i++) { const x = Number(lines[i]); if (used.has(x)) { out.push("已占用"); continue; } let left = null; // x보다 작은 번호 가운데 가장 큰 것 let right = null; // x보다 큰 번호 가운데 가장 작은 것 for (const y of used) { if (y < x && (left === null || y > left)) left = y; if (y > x && (right === null || y < right)) right = y; } out.push(`${left ?? "-"} ${right ?? "-"}`); used.add(x);}out.push(used.size);console.log(out.join("\n"));
TypeScriptmain.ts · 23 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);// TypeScript에도 정렬된 집합이 없어서, 쓰는 번호를 Set에 두고 양옆 이웃은 하나씩 훑어 찾는다const used = new Set<number>();const out: string[] = [];for (let i = 1; i <= n; i++) { const x = Number(lines[i]); if (used.has(x)) { out.push("已占用"); continue; } let left: number | null = null; // x보다 작은 번호 가운데 가장 큰 것 let right: number | null = null; // x보다 큰 번호 가운데 가장 작은 것 for (const y of used) { if (y < x && (left === null || y > left)) left = y; if (y > x && (right === null || y < right)) right = y; } out.push(`${left ?? "-"} ${right ?? "-"}`); used.add(x);}out.push(String(used.size));console.log(out.join("\n"));
JavaMain.java · 27 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.TreeSet;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()); TreeSet<Integer> used = new TreeSet<>(); // 늘 정렬되어 있는 집합 StringBuilder out = new StringBuilder(); for (int i = 0; i < n; i++) { int x = Integer.parseInt(in.readLine().trim()); if (used.contains(x)) { out.append("已占用\n"); continue; } Integer left = used.lower(x); // x보다 작은 것 가운데 가장 큰 것, 없으면 null Integer right = used.higher(x); // x보다 큰 것 가운데 가장 작은 것, 없으면 null out.append(left == null ? "-" : left.toString()).append(' '); out.append(right == null ? "-" : right.toString()).append('\n'); used.add(x); } out.append(used.size()).append('\n'); System.out.print(out); }}
C#Main.cs · 31 行
using System;using System.Collections.Generic;using System.Text;class Program{ static void Main() { int n = int.Parse(Console.ReadLine()); var used = new SortedSet<int>(); // 늘 정렬되어 있는 집합 var sb = new StringBuilder(); for (int i = 0; i < n; i++) { int x = int.Parse(Console.ReadLine()); if (used.Contains(x)) { sb.Append("已占用\n"); continue; } // SortedSet에는 '바로 앞·뒤 값' 메서드가 없어서, x의 왼쪽·오른쪽 구간을 잘라 보고 끝 값을 본다. // 빈 구간의 Max·Min은 0을 돌려주므로 Count를 먼저 확인한다 var below = used.GetViewBetween(int.MinValue, x); var above = used.GetViewBetween(x, int.MaxValue); sb.Append(below.Count > 0 ? below.Max.ToString() : "-").Append(' '); sb.Append(above.Count > 0 ? above.Min.ToString() : "-").Append('\n'); used.Add(x); } sb.Append(used.Count).Append('\n'); Console.Write(sb); }}
C++main.cpp · 24 行
#include <iostream>#include <set>int main() { int n; std::cin >> n; std::set<int> used; // 늘 정렬되어 있는 집합 for (int i = 0; i < n; i++) { int x; std::cin >> x; auto it = used.lower_bound(x); // x 이상인 값이 처음 나오는 자리 if (it != used.end() && *it == x) { std::cout << "已占用\n"; continue; } // it이 오른쪽 이웃이고, 그 바로 앞이 왼쪽 이웃이다. begin()보다 앞으로는 갈 수 없다 if (it != used.begin()) std::cout << *std::prev(it) << ' '; else std::cout << "- "; if (it != used.end()) std::cout << *it << '\n'; else std::cout << "-\n"; used.insert(it, x); // 찾은 자리를 알려 주면 바로 그 앞에 넣는다 } std::cout << used.size() << '\n';}
Cmain.c · 34 行
#include <stdio.h>int main(void) { int n; scanf("%d", &n); /* C에는 정렬된 집합이 없어서, 쓰는 번호를 배열에 두고 양옆 이웃은 하나씩 훑어 찾는다 */ static int used[2000]; int count = 0; for (int k = 0; k < n; k++) { int x; scanf("%d", &x); int taken = 0; int left = 0; /* x보다 작은 번호 가운데 가장 큰 것. 번호는 1부터라서 0이면 아직 없다는 뜻 */ int right = 0; /* x보다 큰 번호 가운데 가장 작은 것. 0이면 아직 없다는 뜻 */ for (int i = 0; i < count; i++) { int y = used[i]; if (y == x) taken = 1; if (y < x && y > left) left = y; if (y > x && (right == 0 || y < right)) right = y; } if (taken) { puts("已占用"); continue; } if (left != 0) printf("%d ", left); else printf("- "); if (right != 0) printf("%d\n", right); else printf("-\n"); used[count] = x; /* 배열은 같은 값도 받아 주므로, 위에서 이미 있는지 먼저 확인했다 */ count++; } printf("%d\n", count); return 0;}
Craykonmain.cray · 29 行
n = 控制台.读行().转数字();// 크레이콘에는 정렬된 집합이 없어서, 쓰는 번호를 목록에 두고 양옆 이웃은 하나씩 훑어 찾는다쓰는번호 = [];循环 (k = 0; k < n; k++){ x = 控制台.读行().转数字(); 如果 (쓰는번호.包含(x)) { 控制台.输出("已占用"); 继续; } 왼쪽 = 空; // x보다 작은 번호 가운데 가장 큰 것 오른쪽 = 空; // x보다 큰 번호 가운데 가장 작은 것 遍历 (y 在 쓰는번호) { 如果 (y < x && (왼쪽 == 空 || y > 왼쪽)) { 왼쪽 = y; } 如果 (y > x && (오른쪽 == 空 || y < 오른쪽)) { 오른쪽 = y; } } 줄 = (왼쪽 == 空 ? "-" : "" + 왼쪽) + " " + (오른쪽 == 空 ? "-" : "" + 오른쪽); 控制台.输出(줄); 쓰는번호.添加(x);}控制台.输出(쓰는번호.数量);