복도에 번호가 붙은 사물함이 번호 순서대로 한 줄로 놓여 있다. 처음에는 쓰는 사물함이 하나도 없다.
학생 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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).
$ 준비됐어요
코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.
다른 언어로는?언어별 풀이 보기 · 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과 크레이콘의 목록은 넣은 차례를 지킬 뿐 정렬해 주지 않아서, 하나씩 훑으며 '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);}콘솔.쓰기(쓰는번호.개수);