{ }codelper
묶기P0349

k명이 모이면 출발하는 체험 부스

과학 축제의 체험 부스는 같은 부스를 고른 학생이 k명 모일 때마다 그 k명을 한 조로 묶어 들여보낸다. 학생 n명이 온 차례대로 이름과 고른 부스 번호 b가 주어진다. 학생이 오면 그 부스의 대기 줄 맨 뒤에 선다. 어떤 부스의 대기 줄이 k명이 되면 그 순간 한 줄에 부스 번호, 쌍점(:), 그리고 그 k명의 이름을 줄 선 차례대로 출력하고 그 부스의 대기 줄을 비운다. 쌍점 뒤와 이름 사이는 빈칸 하나로 띄운다(예: 30: mina seo). 학생이 모두 온 뒤에는 한 줄에 left와 아직 기다리는 학생의 수를 빈칸 하나로 띄워 출력한다. 그 뒤에 기다리는 학생이 있는 부스마다 한 줄씩, 위와 같은 꼴로 부스 번호와 기다리는 이름들을 줄 선 차례대로 출력한다. 이때 부스의 차례는 그 부스 번호가 입력에 처음 나온 차례이다. 그 부스에서 조가 이미 들어간 적이 있어도 처음 나온 차례는 바뀌지 않는다. 기다리는 학생이 없는 부스는 출력하지 않는다.

입력
첫 줄에 n과 k(1 ≤ n ≤ 1,000, 1 ≤ k ≤ 10), 다음 n줄에 이름과 부스 번호 b(1 ≤ b ≤ 1,000,000,000)가 빈칸 하나로 띄워 주어진다.
수는 모두 정수이고 쉼표와 앞의 0 없이 숫자만 주어진다.
이름은 영어 소문자 1~8글자이고, 같은 이름의 학생이 여럿 있을 수 있다
출력
조가 들어갈 때마다 한 줄, 그다음 left 줄, 그다음 기다리는 학생이 있는 부스마다 한 줄(부스 번호는 쉼표 없이 숫자만)
예시 1 · stdin
7 2
mina 30
jun 7
seo 30
dan 7
eli 7
ari 12
bo 30
stdout
30: mina seo
7: jun dan
left 3
30: bo
7: eli
12: ari
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

부스 번호를 열쇠로, 기다리는 이름들의 목록을 값으로 두면 같은 부스끼리 모을 수 있어요.
마지막에 '처음 나온 차례'로 돌아야 해서, 사전이 열쇠를 어떤 차례로 내주는지가 이 문제의 갈림길이에요.
Python의 dict, JS·TS의 Map, Java의 LinkedHashMap, 크레이콘의 사전()(열쇠목록)은 열쇠를 처음 넣은 차례대로 돌아요.
C#의 Dictionary는 도는 차례를 문서에서 약속하지 않고 C++의 unordered_map도 마찬가지라서, 처음 나온 부스 번호를 목록에 따로 적어 두고 그 목록을 돌아요.
C에는 사전이 없으니 부스 번호 배열을 앞에서부터 찾고, 없으면 끝에 더해요.
그러면 배열의 차례가 곧 처음 나온 차례예요.
함정 하나: JS의 보통 객체 {}는 30, 7, 12처럼 정수 꼴인 열쇠를 넣은 차례가 아니라 작은 것부터 돌아요.
예시의 마지막 세 줄이 7: eli, 12: ari, 30: bo 차례가 돼요.
함정 둘: Java의 HashMap은 넣은 차례를 기억하지 않아요.
예시에서 역시 7, 12, 30 차례로 나와요.
함정 셋: C++의 std::map은 언제나 열쇠가 작은 것부터 돌아서 같은 틀린 답이 나와요.
함정 넷: 조가 들어갈 때 열쇠를 아예 지우면(Python del waiting[b]) 그 부스가 다시 나왔을 때 맨 뒤에 새로 들어가요.
예시의 30번 부스는 맨 처음 나왔는데도 30: bo가 마지막 줄로 밀려요.
열쇠는 그대로 두고 값만 빈 목록으로 바꿔요(Python waiting[b] = [], JS names.length = 0, Java names.clear(), C# names.Clear(), C++ names.clear(), 크레이콘 waiting[b] = []).

Pythonmain.py · 15 줄
n, k = map(int, input().split())waiting = {}  # 부스 번호 → 기다리는 이름들. dict는 열쇠를 처음 넣은 차례를 기억한다for _ in range(n):    name, b = input().split()    b = int(b)    if b not in waiting:        waiting[b] = []    waiting[b].append(name)    if len(waiting[b]) == k:        print(f"{b}: {' '.join(waiting[b])}")        waiting[b] = []  # 열쇠는 지우지 않고 값만 비운다: 처음 나온 차례가 남는다print("left", sum(len(names) for names in waiting.values()))for b, names in waiting.items():    if names:        print(f"{b}: {' '.join(names)}")
JavaScriptmain.js · 23 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [n, k] = lines[0].split(" ").map(Number);// Map은 열쇠를 처음 넣은 차례대로 돈다(보통 객체 {}는 정수 열쇠를 작은 것부터 돈다)const waiting = new Map();const out = [];for (let i = 1; i <= n; i++) {  const [name, t] = lines[i].split(" ");  const b = Number(t);  if (!waiting.has(b)) waiting.set(b, []);  const names = waiting.get(b);  names.push(name);  if (names.length === k) {    out.push(`${b}: ${names.join(" ")}`);    names.length = 0; // 열쇠는 지우지 않고 값만 비운다  }}let left = 0;for (const names of waiting.values()) left += names.length;out.push(`left ${left}`);for (const [b, names] of waiting) {  if (names.length > 0) out.push(`${b}: ${names.join(" ")}`);}console.log(out.join("\n"));
TypeScriptmain.ts · 28 줄
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const [n, k] = lines[0].split(" ").map(Number);// Map은 열쇠를 처음 넣은 차례대로 돈다(보통 객체 {}는 정수 열쇠를 작은 것부터 돈다)const waiting = new Map<number, string[]>();const out: string[] = [];for (let i = 1; i <= n; i++) {  const [name, t] = lines[i].split(" ");  const b: number = Number(t);  let names: string[] | undefined = waiting.get(b);  if (names === undefined) {    names = [];    waiting.set(b, names);  }  names.push(name);  if (names.length === k) {    out.push(`${b}: ${names.join(" ")}`);    names.length = 0; // 열쇠는 지우지 않고 값만 비운다  }}let left = 0;for (const names of waiting.values()) left += names.length;out.push(`left ${left}`);for (const [b, names] of waiting) {  if (names.length > 0) out.push(`${b}: ${names.join(" ")}`);}console.log(out.join("\n"));
JavaMain.java · 37 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.LinkedHashMap;import java.util.List;import java.util.Map; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        String[] first = br.readLine().split(" ");        int n = Integer.parseInt(first[0]), k = Integer.parseInt(first[1]);        // LinkedHashMap은 열쇠를 처음 넣은 차례대로 돈다(HashMap은 차례를 지키지 않는다)        Map<Integer, List<String>> waiting = new LinkedHashMap<>();        StringBuilder sb = new StringBuilder();        for (int i = 0; i < n; i++) {            String[] parts = br.readLine().split(" ");            int b = Integer.parseInt(parts[1]);            List<String> names = waiting.computeIfAbsent(b, key -> new ArrayList<>());            names.add(parts[0]);            if (names.size() == k) {                sb.append(b).append(": ").append(String.join(" ", names)).append('\n');                names.clear(); // 열쇠는 지우지 않고 값만 비운다            }        }        int left = 0;        for (List<String> names : waiting.values()) left += names.size();        sb.append("left ").append(left).append('\n');        for (Map.Entry<Integer, List<String>> e : waiting.entrySet()) {            if (!e.getValue().isEmpty()) {                sb.append(e.getKey()).append(": ").append(String.join(" ", e.getValue())).append('\n');            }        }        System.out.print(sb);    }}
C#Main.cs · 43 줄
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        string[] first = Console.ReadLine()!.Split(' ');        int n = int.Parse(first[0]), k = int.Parse(first[1]);        var waiting = new Dictionary<int, List<string>>();        var order = new List<int>(); // Dictionary는 도는 차례를 약속하지 않으므로 처음 나온 차례를 따로 적는다        var sb = new StringBuilder();        for (int i = 0; i < n; i++)        {            string[] parts = Console.ReadLine()!.Split(' ');            int b = int.Parse(parts[1]);            if (!waiting.TryGetValue(b, out List<string>? names))            {                names = new List<string>();                waiting[b] = names;                order.Add(b);            }            names.Add(parts[0]);            if (names.Count == k)            {                sb.Append(b).Append(": ").Append(string.Join(" ", names)).Append('\n');                names.Clear(); // 열쇠는 지우지 않고 값만 비운다            }        }        int left = 0;        foreach (int b in order) left += waiting[b].Count;        sb.Append("left ").Append(left).Append('\n');        foreach (int b in order)        {            if (waiting[b].Count > 0)            {                sb.Append(b).Append(": ").Append(string.Join(" ", waiting[b])).Append('\n');            }        }        Console.Write(sb);    }}
C++main.cpp · 39 줄
#include <iostream>#include <string>#include <unordered_map>#include <vector>using namespace std; static void printGroup(int b, const vector<string>& names) {    cout << b << ':';    for (const string& s : names) cout << ' ' << s;    cout << '\n';} int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, k;    cin >> n >> k;    unordered_map<int, vector<string>> waiting;    vector<int> order; // map은 열쇠 크기순, unordered_map은 제멋대로 돌므로 처음 나온 차례를 따로 적는다    for (int i = 0; i < n; i++) {        string name;        int b;        cin >> name >> b;        if (waiting.find(b) == waiting.end()) order.push_back(b);        vector<string>& names = waiting[b];        names.push_back(name);        if ((int)names.size() == k) {            printGroup(b, names);            names.clear(); // 열쇠는 지우지 않고 값만 비운다        }    }    size_t left = 0;    for (int b : order) left += waiting[b].size();    cout << "left " << left << '\n';    for (int b : order) {        if (!waiting[b].empty()) printGroup(b, waiting[b]);    }    return 0;}
Cmain.c · 43 줄
#include <stdio.h>#include <string.h> /* C에는 사전이 없다: 부스 번호를 처음 나온 차례대로 배열에 적고 앞에서부터 찾는다 */int booth[1000];        /* booth[g]: g번째로 처음 나온 부스 번호 */int count[1000];        /* 그 부스에서 기다리는 사람 수 */char names[1000][10][9]; /* 그 부스에서 기다리는 이름들(k ≤ 10, 이름 ≤ 8글자) */ static void print_group(int g) {    printf("%d:", booth[g]);    for (int j = 0; j < count[g]; j++) printf(" %s", names[g][j]);    printf("\n");} int main(void) {    int n, k, groups = 0;    if (scanf("%d %d", &n, &k) != 2) return 0;    for (int i = 0; i < n; i++) {        char name[9];        int b;        if (scanf("%8s %d", name, &b) != 2) return 0;        int g = 0;        while (g < groups && booth[g] != b) g++;        if (g == groups) {            booth[groups] = b;            count[groups] = 0;            groups++;        }        strcpy(names[g][count[g]], name);        count[g]++;        if (count[g] == k) {            print_group(g);            count[g] = 0; /* 자리는 그대로 두고 수만 0으로 */        }    }    int left = 0;    for (int g = 0; g < groups; g++) left += count[g];    printf("left %d\n", left);    for (int g = 0; g < groups; g++) {        if (count[g] > 0) print_group(g);    }    return 0;}
Craykonmain.cray · 32 줄
첫줄 = 콘솔.수들읽기();n = 첫줄[0];k = 첫줄[1];waiting = 사전(); // 부스 번호 → 기다리는 이름들. 사전은 열쇠를 처음 넣은 차례를 기억한다반복 (i = 0; i < n; i++){    parts = 콘솔.줄읽기().나누기();    b = parts[1].수로변환();    만약 (!waiting.있나(b))    {        waiting[b] = [];    }    waiting[b].넣기(parts[0]);    만약 (waiting[b].개수 == k)    {        콘솔.쓰기(b + ": " + waiting[b].잇기(" "));        waiting[b] = []; // 열쇠는 빼지 않고 값만 비운다    }}left = 0;각각 (b 에서 waiting.열쇠목록){    left += waiting[b].개수;}콘솔.쓰기("left " + left);각각 (b 에서 waiting.열쇠목록){    만약 (waiting[b].개수 > 0)    {        콘솔.쓰기(b + ": " + waiting[b].잇기(" "));    }}