{ }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()/标准输入传入(运行中不会等待输入)。

$ 就绪

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

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

以摊位编号为键、等候者名字的列表为值,就能把同一摊位的学生归到一起。
最后要按“第一次出现的顺序”遍历,所以字典按什么顺序给出键,就是本题的分岔口。
Python 的 dict、JS 和 TS 的 Map、Java 的 LinkedHashMap、Craykon 的 字典()(键列表)都按键第一次放入的顺序遍历。
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()、Craykon 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].连接(" "));    }}