{ }codelper
キーの存在辞書で数える合計P0179

小さな図書館の貸出台帳

小さな図書館には本が一冊ずつしかなく、貸出と返却を一行ずつ台帳に書く。
一行目に記録の数 q が、続く q 行に記録「out 日 本」または「in 日 本」が与えられる。
日は図書館を開いた日を 0 として数えた整数で、記録は日が減らない順に与えられる。
「out d b」は d 日に本 b を貸し出す記録である。
その本がすでに貸出中なら「busy b」を一行出力し、この記録はなかったことにする。
そうでなければ本 b は d 日から貸出中になり、本 b の貸出回数が 1 増える。
「in d b」は d 日に本 b を返してもらう記録である。
その本が貸出中でなければ「unknown b」を一行出力し、この記録はなかったことにする。
そうでなければ、貸し出した日を s 日として d − s 日間借りたことになり、本 b は再び図書館にある状態になる。
借りた日数が 14 日以下なら延滞料はなく、14 日を超えると超えた一日ごとに 100 ウォンの延滞料を取る。
すべての記録を処理したあとで「fee 延滞料の合計」を一行出力せよ。
延滞料の合計は返却された貸出の延滞料をすべて足した値で、最後まで返ってこなかった本は含めない。
最後の行には、貸出回数がいちばん多い本を「top 本 回数」として出力せよ。
回数が同じ本が複数あれば、名前が辞書順で先の本を出力する。
貸出が一度もなかったら、最後の行に「top none」を出力する。
辞書順は先頭の文字から比べてアルファベットが前のほうが先で、一方がもう一方の先頭部分なら短いほうが先である。

入力
1 ≤ q ≤ 10,000、0 ≤ 日 ≤ 1,000,000(整数、減らない)。
本の名前は英小文字 1 ~ 10 文字で、異なる本は 1,000 冊以下。
最初の単語は out か in
出力
busy・unknown の行を記録の順に出力し、続いて「fee 延滞料の合計」と「top 本 回数」(または「top none」)
例 1 · stdin
8
out 1 moby
out 3 dune
out 5 moby
in 15 moby
in 20 dune
out 20 dune
in 40 emma
in 50 dune
stdout
busy moby
unknown emma
fee 1900
top dune 2
例 2 · stdin
4
out 0 atlas
out 0 atlas
in 16 atlas
in 16 atlas
stdout
busy atlas
unknown atlas
fee 200
top atlas 1
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

辞書を二つ使います。
貸出中の本 → 貸した日 の辞書でキーがあるかを確かめ、本 → 貸出回数 の辞書で数えます。
キーがあるか:Python book in on_loan、JS・TS onLoan.has(book)、Java containsKey、C# ContainsKey(取り除きながら値を取り出す Remove(book, out int start) もあります)、C++ find(book) != end()、Craykon onLoan.含む(book)(韓国語 있나)。
C には辞書がないので本の表を順に探し、0 日も本当の日なので「貸出中でない」は −1 で表します。
落とし穴その一:0 日に貸した本は値が 0 なので、Python if on_loan.get(book): や JS if (onLoan.get(book)) のように値の真偽で聞くと貸出中でないように見えます。
キーがあるかを聞かなければなりません。
C++ の if (onLoan[book]) も値で聞いているので同じ落とし穴で、そのうえない本を 0 日で新しく入れてしまいます。
数える:Python loans.get(book, 0) + 1、JS・TS (loans.get(book) ?? 0) + 1、Java merge(book, 1, Integer::sum)、C# GetValueOrDefault(book, 0) + 1、C++ loans[book]++、Craykon は 含む で先に 0 を入れてから足します。
落とし穴その二:Python の Counter.most_common(1) は回数が同じとき先に数えた本を返すので、辞書順ではありません。
min(loans, key=lambda b: (-loans[b], b)) のように名前まで比べるか、Java の TreeMap・C++ の std::map のようにキーの順に回って大きいときだけ入れ替えます。
落とし穴その三:延滞料の合計は 1,000 冊 × 約 1 億ウォン = 約 1000 億ウォンまで行くので、Java・C# の int、C の int ではあふれます。
long・long long を使い、Python・JS・TS・Craykon は 2^53 未満なのでそのままで大丈夫です。
14 日は延滞ではないので max(0, 借りた日数 − 14) × 100 です。

Pythonmain.py · 31 行
import sys lines = sys.stdin.read().split("\n")q = int(lines[0])on_loan = {}      # 책 → 빌려 간 날loans = {}        # 책 → 빌려 간 횟수fee = 0out = []for line in lines[1:1 + q]:    kind, day, book = line.split()    day = int(day)    if kind == "out":        if book in on_loan:             # 빌려 간 날이 0일 수도 있으니 값이 아니라 열쇠가 있는지 묻는다            out.append(f"busy {book}")            continue        on_loan[book] = day        loans[book] = loans.get(book, 0) + 1    else:        if book not in on_loan:            out.append(f"unknown {book}")            continue        kept = day - on_loan.pop(book)        fee += max(0, kept - 14) * 100 out.append(f"fee {fee}")if loans:    best = min(loans, key=lambda b: (-loans[b], b))   # 많이 빌린 책, 같으면 사전 순으로 앞선 책    out.append(f"top {best} {loans[best]}")else:    out.append("top none")print("\n".join(out))
JavaScriptmain.js · 35 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const q = Number(lines[0]);const onLoan = new Map(); // 책 → 빌려 간 날const loans = new Map(); // 책 → 빌려 간 횟수let fee = 0;const out = [];for (const line of lines.slice(1, 1 + q)) {  const [kind, d, book] = line.trim().split(" ");  const day = Number(d);  if (kind === "out") {    // 빌려 간 날이 0이면 get()이 0이라 거짓이 된다: has()로 열쇠가 있는지 묻는다    if (onLoan.has(book)) {      out.push(`busy ${book}`);      continue;    }    onLoan.set(book, day);    loans.set(book, (loans.get(book) ?? 0) + 1);  } else {    if (!onLoan.has(book)) {      out.push(`unknown ${book}`);      continue;    }    const kept = day - onLoan.get(book);    onLoan.delete(book);    fee += Math.max(0, kept - 14) * 100;  }} out.push(`fee ${fee}`);let best = null;for (const [book, n] of loans) {  if (best === null || n > loans.get(best) || (n === loans.get(best) && book < best)) best = book;}out.push(best === null ? "top none" : `top ${best} ${loans.get(best)}`);console.log(out.join("\n"));
TypeScriptmain.ts · 40 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const q = Number(lines[0]);const onLoan = new Map<string, number>(); // 책 → 빌려 간 날const loans = new Map<string, number>(); // 책 → 빌려 간 횟수let fee = 0;const out: string[] = [];for (const line of lines.slice(1, 1 + q)) {  const [kind, d, book] = line.trim().split(" ");  const day = Number(d);  if (kind === "out") {    // 빌려 간 날이 0이면 get()이 0이라 거짓이 된다: has()로 열쇠가 있는지 묻는다    if (onLoan.has(book)) {      out.push(`busy ${book}`);      continue;    }    onLoan.set(book, day);    loans.set(book, (loans.get(book) ?? 0) + 1);  } else {    const start = onLoan.get(book);    if (start === undefined) {      out.push(`unknown ${book}`);      continue;    }    onLoan.delete(book);    fee += Math.max(0, day - start - 14) * 100;  }} out.push(`fee ${fee}`);let best: string | null = null;let most = 0;for (const [book, n] of loans) {  if (best === null || n > most || (n === most && book < best)) {    best = book;    most = n;  }}out.push(best === null ? "top none" : `top ${best} ${most}`);console.log(out.join("\n"));
JavaMain.java · 44 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int q = Integer.parseInt(br.readLine().trim());        Map<String, Integer> onLoan = new HashMap<>(); // 책 → 빌려 간 날        Map<String, Integer> loans = new TreeMap<>();  // 책 → 빌려 간 횟수(사전 순으로 돈다)        long fee = 0; // 1,000권 × 약 1억 원이라 int로는 넘친다        StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            String[] p = br.readLine().trim().split(" ");            int day = Integer.parseInt(p[1]);            String book = p[2];            if (p[0].equals("out")) {                if (onLoan.containsKey(book)) {                    out.append("busy ").append(book).append('\n');                    continue;                }                onLoan.put(book, day);                loans.merge(book, 1, Integer::sum);            } else {                Integer start = onLoan.remove(book); // 없으면 null                if (start == null) {                    out.append("unknown ").append(book).append('\n');                    continue;                }                fee += Math.max(0, day - start - 14) * 100L;            }        }        out.append("fee ").append(fee).append('\n');        String best = null;        int most = 0;        for (Map.Entry<String, Integer> e : loans.entrySet()) {            if (e.getValue() > most) { // 사전 순으로 도니 같으면 앞의 것이 남는다                best = e.getKey();                most = e.getValue();            }        }        out.append(best == null ? "top none" : "top " + best + " " + most).append('\n');        System.out.print(out);    }}
C#Main.cs · 53 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        int q = int.Parse(Console.ReadLine().Trim());        var onLoan = new Dictionary<string, int>(); // 책 → 빌려 간 날        var loans = new Dictionary<string, int>();  // 책 → 빌려 간 횟수        long fee = 0; // 1,000권 × 약 1억 원이라 int로는 넘친다        var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            string[] p = Console.ReadLine().Trim().Split(' ');            int day = int.Parse(p[1]);            string book = p[2];            if (p[0] == "out")            {                if (onLoan.ContainsKey(book))                {                    sb.Append($"busy {book}\n");                    continue;                }                onLoan[book] = day;                loans[book] = loans.GetValueOrDefault(book, 0) + 1;            }            else            {                if (!onLoan.Remove(book, out int start)) // 있으면 빼면서 값을 꺼낸다                {                    sb.Append($"unknown {book}\n");                    continue;                }                fee += Math.Max(0, day - start - 14) * 100L;            }        }        sb.Append($"fee {fee}\n");        string best = null;        int most = 0;        foreach (var (book, n) in loans)        {            if (best == null || n > most || (n == most && string.CompareOrdinal(book, best) < 0))            {                best = book;                most = n;            }        }        sb.Append(best == null ? "top none\n" : $"top {best} {most}\n");        Console.Write(sb);    }}
C++main.cpp · 36 行
#include <algorithm>#include <iostream>#include <map>#include <string> int main() {    std::ios::sync_with_stdio(false);    int q;    std::cin >> q;    std::map<std::string, int> onLoan; // 책 → 빌려 간 날    std::map<std::string, int> loans;  // 책 → 빌려 간 횟수(열쇠 순으로 돈다)    long long fee = 0; // 1,000권 × 약 1억 원이라 int로는 넘친다    std::string out;    for (int i = 0; i < q; i++) {        std::string kind, book;        int day;        std::cin >> kind >> day >> book;        auto it = onLoan.find(book); // onLoan[book]으로 물으면 없는 책이 0일로 생겨 버린다        if (kind == "out") {            if (it != onLoan.end()) { out += "busy " + book + "\n"; continue; }            onLoan[book] = day;            loans[book]++;        } else {            if (it == onLoan.end()) { out += "unknown " + book + "\n"; continue; }            fee += std::max(0, day - it->second - 14) * 100LL;            onLoan.erase(it);        }    }    out += "fee " + std::to_string(fee) + "\n";    std::string best;    int most = 0;    for (const auto& [book, n] : loans)        if (n > most) { best = book; most = n; } // 사전 순으로 도니 같으면 앞의 것이 남는다    out += most == 0 ? "top none\n" : "top " + best + " " + std::to_string(most) + "\n";    std::cout << out;}
Cmain.c · 49 行
#include <stdio.h>#include <string.h> /* C에는 사전이 없다: 책은 많아야 1,000권이라 책 표를 차례로 찾는다 */static char title[1000][11];static int out_day[1000];   /* 빌려 간 날, 빌려 가지 않았으면 -1 */static int loans[1000];     /* 빌려 간 횟수 */static int count = 0; static int find_or_add(const char* book) {    for (int i = 0; i < count; i++)        if (strcmp(title[i], book) == 0) return i;    strcpy(title[count], book);    out_day[count] = -1; /* 0일에 빌려 갈 수 있으니 "없음"은 -1로 둔다 */    loans[count] = 0;    return count++;} int main(void) {    int q;    scanf("%d", &q);    long long fee = 0; /* 1,000권 × 약 1억 원이라 int로는 넘친다 */    for (int e = 0; e < q; e++) {        char kind[4], book[11];        int day;        scanf("%3s %d %10s", kind, &day, book);        int b = find_or_add(book);        if (strcmp(kind, "out") == 0) {            if (out_day[b] >= 0) { printf("busy %s\n", book); continue; }            out_day[b] = day;            loans[b]++;        } else {            if (out_day[b] < 0) { printf("unknown %s\n", book); continue; }            int late = day - out_day[b] - 14;            if (late > 0) fee += late * 100LL;            out_day[b] = -1;        }    }    printf("fee %lld\n", fee);    int best = -1;    for (int i = 0; i < count; i++) {        if (loans[i] == 0) continue; /* 반납 기록만 있는 책은 빼고 */        if (best < 0 || loans[i] > loans[best] || (loans[i] == loans[best] && strcmp(title[i], title[best]) < 0))            best = i;    }    if (best < 0) printf("top none\n");    else printf("top %s %d\n", title[best], loans[best]);    return 0;}
Craykonmain.cray · 60 行
q = コンソール.行読み().数に();onLoan = 辞書(); // 책 → 빌려 간 날loans = 辞書(); // 책 → 빌려 간 횟수fee = 0;繰り返す (e = 0; e < q; e++){    p = コンソール.行読み().空白除去().分ける();    day = p[1].数に();    book = p[2];    もし (p[0] == "out")    {        もし (onLoan.含む(book))        {            コンソール.書く("busy " + book);        }        でなければ        {            onLoan[book] = day;            もし (!loans.含む(book))            {                loans[book] = 0;            }            loans[book] = loans[book] + 1;        }    }    でなければ もし (!onLoan.含む(book))    {        コンソール.書く("unknown " + book);    }    でなければ    {        late = day - onLoan[book] - 14;        もし (late > 0)        {            fee = fee + late * 100;        }        onLoan.取り除く(book);    }}コンソール.書く("fee " + fee); best = なし;most = 0;それぞれ (book の中 loans.キー一覧){    n = loans[book];    もし (best == なし || n > most || n == most && book < best)    {        best = book;        most = n;    }}もし (best == なし){    コンソール.書く("top none");}でなければ{    コンソール.書く("top " + best + " " + most);}