{ }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);}