{ }codelper
累计更新字典按键排序P0178

分类预算账本

一个月的家庭账本给每个类别设了预算上限,花的钱一行一行记下来。
第一行给出设了预算的类别数 c,接下来 c 行给出 "类别 上限"。
然后给出记录数 n,接下来 n 行给出记录 "类别 金额"。
金额为正表示花出的钱,为负表示退回的钱(退款),也可能为 0。
没有设预算的类别也可能出现在记录中,这样的类别上限为 0。
每个类别的花费从 0 开始,依次加上该类别的金额。
对每条记录输出一行 "总累计 类别累计"。
总累计是到目前为止所有记录金额之和,类别累计是加上这条记录后该类别的花费。
如果这条记录之前该类别的花费不超过上限,而加上这条记录后大于上限,就在该行末尾加一个空格和 "over"。
已经超过上限后又继续花的记录不加 over。
如果退款使它回到上限以内,之后又再次超过上限,那条记录要再加 over。
所有记录之后,把设了预算的类别和记录中出现的类别全部按名字的字典序每行输出 "类别 花费 上限 剩余"。
剩余是上限减去花费的值,可能为负。
字典序从第一个字母开始比较,字母靠前的在前;如果一个名字是另一个的开头部分,短的在前。

输入
1 ≤ c ≤ 100,1 ≤ n ≤ 3000,0 ≤ 上限 ≤ 10^12,−10^9 ≤ 金额 ≤ 10^9(整数)。
类别名由 1 ~ 10 个英文小写字母组成,预算行的名字互不相同,出现的类别合计不超过 100 个
输出
n 行 "总累计 类别累计"(超过的那一刻末尾加 " over"),然后每个类别按字典序一行 "类别 花费 上限 剩余"
样例 1 · stdin
3
food 50000
rent 300000
book 20000
6
food 30000
rent 300000
food 25000
game 5000
food -10000
food 6000
stdout
30000 30000
330000 300000
355000 55000 over
360000 5000 over
350000 45000
356000 51000 over
book 0 20000 20000
food 51000 50000 -1000
game 5000 0 -5000
rent 300000 300000 0
样例 2 · stdin
1
bus 0
4
bus 0
bus -500
bus 500
bus 1
stdout
0 0
-500 -500
0 0
1 1 over
bus 1 0 -1
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

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

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

这道题同时维护两种累计:一个总的,以及字典里每个类别一个。
总累计可达 3,000 × 10亿 = 3万亿,C 的 int、Java/C# 的 int 会溢出。
C 用 long long,Java/C# 用 long;Python、JS、TS、Craykon 直接就行(3万亿小于 2^53)。
更新字典就是“没有就从 0 开始”:Python spent.get(name, 0),JS/TS spent.get(name) ?? 0,Java getOrDefault(name, 0L),C# GetValueOrDefault(name, 0),C++ spent[name](没有时会生成 0),Craykon 要先执行 如果 (!spent.包含(name)) { spent[name] = 0; }(韩文 있나)。
上限字典在 C++ 里用 limit[name] 读取会把不存在的类别新加进字典,所以先用 count(name) 查询。
C 没有字典,按顺序查名字表。
最后的表按键的顺序遍历:Python sorted(spent),JS/TS [...spent.keys()].sort(),Java TreeMap,C# SortedDictionary(StringComparer.Ordinal),C++ std::map(始终有序),Craykon 把 键列表 用 排序()(韩文 열쇠목록、정렬),C 用 qsort 加 strcmp。
陷阱一:JS 的 Map、Python 的 dict、Craykon 的字典按插入顺序遍历,不排序就会按预算行的顺序输出。
陷阱二:用 Python limit[name] 读取没有预算的类别会 KeyError,Java long cap = limit.get(name) 拆箱 null 时抛出 NullPointerException。
陷阱三:over 标记的是“超过的那一刻”而不是“超过期间”,所以条件是 before <= cap && after > cap。
陷阱四:总累计用 int,一超过约 21 亿就会绕成负数。

Pythonmain.py · 29 行
import sys lines = sys.stdin.read().split("\n")c = int(lines[0])limit = {}spent = {}for line in lines[1:1 + c]:    name, cap = line.split()    limit[name] = int(cap)    spent[name] = 0n = int(lines[1 + c])total = 0out = []for line in lines[2 + c:2 + c + n]:    name, amount = line.split()    amount = int(amount)    cap = limit.get(name, 0)            # 예산을 정하지 않은 항목은 한도 0    before = spent.get(name, 0)    spent[name] = before + amount    total += amount                     # 전체 누적    row = f"{total} {spent[name]}"    if before <= cap < spent[name]:     # 한도 안에서 밖으로 넘어간 순간만        row += " over"    out.append(row) for name in sorted(spent):              # 열쇠를 사전 순으로    cap = limit.get(name, 0)    out.append(f"{name} {spent[name]} {cap} {cap - spent[name]}")print("\n".join(out))
JavaScriptmain.js · 30 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const c = Number(lines[0]);const limit = new Map();const spent = new Map();for (const line of lines.slice(1, 1 + c)) {  const [name, cap] = line.trim().split(" ");  limit.set(name, Number(cap));  spent.set(name, 0);}const n = Number(lines[1 + c]);let total = 0;const out = [];for (const line of lines.slice(2 + c, 2 + c + n)) {  const [name, a] = line.trim().split(" ");  const amount = Number(a);  const cap = limit.get(name) ?? 0; // 예산을 정하지 않은 항목은 한도 0  const before = spent.get(name) ?? 0;  const after = before + amount;  spent.set(name, after);  total += amount;  out.push(`${total} ${after}` + (before <= cap && after > cap ? " over" : ""));} // Map은 넣은 차례를 지키므로 열쇠를 따로 정렬해야 사전 순이 된다for (const name of [...spent.keys()].sort()) {  const cap = limit.get(name) ?? 0;  const s = spent.get(name) ?? 0;  out.push(`${name} ${s} ${cap} ${cap - s}`);}console.log(out.join("\n"));
TypeScriptmain.ts · 31 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const c = Number(lines[0]);const limit = new Map<string, number>();const spent = new Map<string, number>();for (const line of lines.slice(1, 1 + c)) {  const [name, cap] = line.trim().split(" ");  limit.set(name, Number(cap));  spent.set(name, 0);}const n = Number(lines[1 + c]);let total = 0;const out: string[] = [];for (const line of lines.slice(2 + c, 2 + c + n)) {  const [name, a] = line.trim().split(" ");  const amount = Number(a);  const cap = limit.get(name) ?? 0; // 예산을 정하지 않은 항목은 한도 0  const before = spent.get(name) ?? 0;  const after = before + amount;  spent.set(name, after);  total += amount;  out.push(`${total} ${after}` + (before <= cap && after > cap ? " over" : ""));} // Map은 넣은 차례를 지키므로 열쇠를 따로 정렬해야 사전 순이 된다for (const name of [...spent.keys()].sort()) {  const cap = limit.get(name) ?? 0;  const s = spent.get(name) ?? 0;  out.push(`${name} ${s} ${cap} ${cap - s}`);}console.log(out.join("\n"));
JavaMain.java · 37 行
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 c = Integer.parseInt(br.readLine().trim());        Map<String, Long> limit = new HashMap<>();        Map<String, Long> spent = new TreeMap<>(); // TreeMap은 열쇠를 사전 순으로 지킨다        for (int i = 0; i < c; i++) {            String[] p = br.readLine().trim().split(" ");            limit.put(p[0], Long.parseLong(p[1]));            spent.put(p[0], 0L);        }        int n = Integer.parseInt(br.readLine().trim());        long total = 0; // 3,000 × 10억 = 3조라 int로는 넘친다        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) {            String[] p = br.readLine().trim().split(" ");            long amount = Long.parseLong(p[1]);            long cap = limit.getOrDefault(p[0], 0L); // 예산을 정하지 않은 항목은 한도 0            long before = spent.getOrDefault(p[0], 0L);            long after = before + amount;            spent.put(p[0], after);            total += amount;            out.append(total).append(' ').append(after);            if (before <= cap && after > cap) out.append(" over");            out.append('\n');        }        for (Map.Entry<String, Long> e : spent.entrySet()) {            long cap = limit.getOrDefault(e.getKey(), 0L);            out.append(e.getKey()).append(' ').append(e.getValue()).append(' ')               .append(cap).append(' ').append(cap - e.getValue()).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 41 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        int c = int.Parse(Console.ReadLine().Trim());        var limit = new Dictionary<string, long>();        var spent = new SortedDictionary<string, long>(StringComparer.Ordinal); // 열쇠를 글자 코드 순으로        for (int i = 0; i < c; i++)        {            string[] p = Console.ReadLine().Trim().Split(' ');            limit[p[0]] = long.Parse(p[1]);            spent[p[0]] = 0;        }        int n = int.Parse(Console.ReadLine().Trim());        long total = 0; // 3,000 × 10억 = 3조라 int로는 넘친다        var sb = new StringBuilder();        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine().Trim().Split(' ');            long amount = long.Parse(p[1]);            long cap = limit.GetValueOrDefault(p[0], 0); // 예산을 정하지 않은 항목은 한도 0            long before = spent.GetValueOrDefault(p[0], 0);            long after = before + amount;            spent[p[0]] = after;            total += amount;            sb.Append(total).Append(' ').Append(after);            if (before <= cap && after > cap) sb.Append(" over");            sb.Append('\n');        }        foreach (var (name, s) in spent)        {            long cap = limit.GetValueOrDefault(name, 0);            sb.Append($"{name} {s} {cap} {cap - s}\n");        }        Console.Write(sb);    }}
C++main.cpp · 38 行
#include <iostream>#include <map>#include <string> int main() {    std::ios::sync_with_stdio(false);    int c, n;    std::cin >> c;    std::map<std::string, long long> limit;    std::map<std::string, long long> spent; // std::map은 열쇠를 정렬해 둔다    for (int i = 0; i < c; i++) {        std::string name;        long long cap;        std::cin >> name >> cap;        limit[name] = cap;        spent[name] = 0;    }    std::cin >> n;    long long total = 0; // 3,000 × 10억 = 3조라 int로는 넘친다    std::string out;    for (int i = 0; i < n; i++) {        std::string name;        long long amount;        std::cin >> name >> amount;        // count로 먼저 묻는다: limit[name]이라고 읽으면 없는 열쇠가 0으로 새로 생긴다        long long cap = limit.count(name) ? limit[name] : 0;        long long before = spent[name]; // 없으면 []가 0으로 만들어 준다        long long after = before + amount;        spent[name] = after;        total += amount;        out += std::to_string(total) + " " + std::to_string(after) + (before <= cap && after > cap ? " over" : "") + "\n";    }    for (const auto& [name, s] : spent) {        long long cap = limit.count(name) ? limit[name] : 0;        out += name + " " + std::to_string(s) + " " + std::to_string(cap) + " " + std::to_string(cap - s) + "\n";    }    std::cout << out;}
Cmain.c · 50 行
#include <stdio.h>#include <stdlib.h>#include <string.h> /* C에는 사전이 없다: 항목은 많아야 100개라 이름 표를 차례로 찾는다 */typedef struct {    char name[11];    long long limit, spent; /* 3,000 × 10억 = 3조라 int로는 넘친다 */} Item; static Item items[100];static int count = 0; static int find_or_add(const char* name) {    for (int i = 0; i < count; i++)        if (strcmp(items[i].name, name) == 0) return i;    strcpy(items[count].name, name);    items[count].limit = 0; /* 예산을 정하지 않은 항목은 한도 0 */    items[count].spent = 0;    return count++;} static int by_name(const void* a, const void* b) {    return strcmp(((const Item*)a)->name, ((const Item*)b)->name);} int main(void) {    int c, n;    char name[11];    long long x;    scanf("%d", &c);    for (int i = 0; i < c; i++) {        scanf("%10s %lld", name, &x);        items[find_or_add(name)].limit = x;    }    scanf("%d", &n);    long long total = 0;    for (int i = 0; i < n; i++) {        scanf("%10s %lld", name, &x);        Item* it = &items[find_or_add(name)];        long long before = it->spent;        it->spent += x;        total += x;        printf("%lld %lld%s\n", total, it->spent, before <= it->limit && it->spent > it->limit ? " over" : "");    }    qsort(items, count, sizeof items[0], by_name);    for (int i = 0; i < count; i++)        printf("%s %lld %lld %lld\n", items[i].name, items[i].spent, items[i].limit, items[i].limit - items[i].spent);    return 0;}
Craykonmain.cray · 43 行
c = 控制台.读行().转数字();limit = 字典();spent = 字典();循环 (i = 0; i < c; i++){    p = 控制台.读行().去空白().分割();    limit[p[0]] = p[1].转数字();    spent[p[0]] = 0;}n = 控制台.读行().转数字();total = 0;循环 (i = 0; i < n; i++){    p = 控制台.读行().去空白().分割();    name = p[0];    amount = p[1].转数字();    // 없는 열쇠를 읽으면 멈추니 먼저 넣어 둔다(예산을 정하지 않은 항목은 한도 0)    如果 (!limit.包含(name))    {        limit[name] = 0;    }    如果 (!spent.包含(name))    {        spent[name] = 0;    }    before = spent[name];    spent[name] = before + amount;    total = total + amount;    row = total + " " + spent[name];    如果 (before <= limit[name] && spent[name] > limit[name])    {        row = row + " over";    }    控制台.输出(row);} // 열쇠목록은 넣은 차례라 사전 순으로 정렬한다names = spent.键列表;names.排序();遍历 (name 在 names){    控制台.输出(name + " " + spent[name] + " " + limit[name] + " " + (limit[name] - spent[name]));}