{ }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].つなぐ(" "));    }}