{ }codelper
更新字典键是否存在P0388

合并相册标签

相册里的每张照片都贴着一个标签。
相册为每个标签记住贴着这个标签的照片数。
至少贴在一张照片上的标签叫做存在的标签,一开始没有存在的标签。
依次处理 q 条命令。
add t k 表示贴着标签 t 的照片增加 k 张。
如果标签 t 原来不存在,就新建它。
然后输出标签 t 的照片数。
move a b 表示把贴着标签 a 的照片全部改贴标签 b。
如果标签 a 不存在,什么都不改变,输出 0。
如果标签 a 存在并且 a 与 b 相同,什么都不改变,输出这个标签的照片数。
如果标签 a 存在并且 a 与 b 不同,把 a 的照片数加到 b 的照片数上,并去掉标签 a。
如果标签 b 原来不存在,就新建它。
然后输出标签 b 的照片数。
处理完所有命令后,输出存在的标签的个数。

输入
第一行给出命令的条数 q(1 ≤ q ≤ 2,000)。
接下来 q 行每行给出一条命令。
命令的形式是 add t k 或 move a b,词与词之间用一个空格隔开。
标签名 t、a、b 是只由英文小写字母组成的字符串,长度为 1 到 10。
k 是整数(1 ≤ k ≤ 1,000)。
输出
对每条命令,按命令给出的顺序每行输出一个上面规定的数。
在最后一行输出处理完所有命令后存在的标签的个数。
样例 1 · stdin
8
add cat 3
add dog 2
move cat pet
move dog pet
move bird dog
add cat 4
move cat cat
move pet cat
stdout
3
2
3
5
0
4
4
9
1
main.py
输入(stdin)

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

$ 就绪

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

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

从字典里删除键的同时取出它的值的方法,以及往可能不存在的键上加值的方法,各语言并不相同。
Python 的 moved = tags.pop(a) 在删除的同时返回值,再用 tags[b] = tags.get(b, 0) + moved 相加。
Java 的 tags.remove(a) 返回被删除的值(不存在时为 null),tags.merge(b, moved, Integer::sum) 在键不存在时放入、存在时相加。
C# 用 tags.Remove(a, out int moved) 接收值。
相加时先用 TryGetValue 取出(不存在时为 0)。
JavaScript 和 TypeScript 的 Map 的 delete 返回的不是值而是 true/false,所以先用 get 读出来,再用 (tags.get(b) ??
0) + moved 相加。
C++ 从 find 找到的位置读出值,再用 erase 删除。
tags[b] += moved 会把不存在的键建成 0,所以可以直接用。
C 没有字典,所以把名字数组和数量数组并排放着,从前往后查找。
Craykon 中移除键的操作返回的不是值而是真假,所以要先读出来;不存在的键不能读,所以要先确认键是否存在,不存在就先放入 0,然后再相加。
陷阱一:移到同一个标签。
示例 1 的第七条命令 move cat cat 输出 4,什么都不改变。
如果不检查 a 和 b 是否相同就“加到 b 上再删除 a”,cat 会先变成 8 然后被整个删除,于是这一行输出 8,下一行输出的不是 9 而是 5。
陷阱二:覆盖。
第四条命令 move dog pet 中 pet 已经有 3 张,所以变成 5。
如果不相加而用 Java 的 put 放入,会得到 2。
陷阱三:只是询问就产生的键。
在 C++ 中用 tags[a] == 0 来问标签是否存在,会把不存在的键建成 0。
第五条命令 move bird dog 之后留下一个 0 张照片的 bird,最后一行不是 1 而是 2。
陷阱四:从不存在的标签取值。
同样在第五条命令,不检查就调用 Python 的 tags.pop(a) 会因 KeyError 而停止。
陷阱五:往不存在的键上加值。
第三条命令 move cat pet 中 pet 还不存在。
在 C# 中写 tags[b] += moved 会因 KeyNotFoundException 而停止。

Pythonmain.py · 24 行
import sys lines = sys.stdin.read().split("\n")q = int(lines[0])tags = {}out = []for i in range(1, q + 1):    parts = lines[i].split()    if parts[0] == "add":        t = parts[1]        tags[t] = tags.get(t, 0) + int(parts[2])        out.append(str(tags[t]))    else:        a, b = parts[1], parts[2]        if a not in tags:            out.append("0")        elif a == b:            out.append(str(tags[a]))        else:            moved = tags.pop(a)  # 지우면서 값을 돌려받는다            tags[b] = tags.get(b, 0) + moved            out.append(str(tags[b]))out.append(str(len(tags)))print("\n".join(out))
JavaScriptmain.js · 27 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const q = Number(lines[0]);const tags = new Map();const out = [];for (let i = 1; i <= q; i++) {  const parts = lines[i].trim().split(" ");  if (parts[0] === "add") {    const t = parts[1];    tags.set(t, (tags.get(t) ?? 0) + Number(parts[2]));    out.push(String(tags.get(t)));  } else {    const a = parts[1];    const b = parts[2];    if (!tags.has(a)) {      out.push("0");    } else if (a === b) {      out.push(String(tags.get(a)));    } else {      const moved = tags.get(a); // delete는 값이 아니라 true/false를 돌려준다      tags.delete(a);      tags.set(b, (tags.get(b) ?? 0) + moved);      out.push(String(tags.get(b)));    }  }}out.push(String(tags.size));console.log(out.join("\n"));
TypeScriptmain.ts · 31 行
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const q: number = Number(lines[0]);const tags: Map<string, number> = new Map();const out: string[] = [];for (let i = 1; i <= q; i++) {  const parts: string[] = lines[i].trim().split(" ");  if (parts[0] === "add") {    const t: string = parts[1];    const count: number = (tags.get(t) ?? 0) + Number(parts[2]);    tags.set(t, count);    out.push(String(count));  } else {    const a: string = parts[1];    const b: string = parts[2];    const moved: number | undefined = tags.get(a); // 없으면 undefined    if (moved === undefined) {      out.push("0");    } else if (a === b) {      out.push(String(moved));    } else {      tags.delete(a); // delete는 값이 아니라 true/false를 돌려준다      const count: number = (tags.get(b) ?? 0) + moved;      tags.set(b, count);      out.push(String(count));    }  }}out.push(String(tags.size));console.log(out.join("\n"));
JavaMain.java · 36 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.HashMap;import java.util.Map; 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> tags = new HashMap<>();        StringBuilder sb = new StringBuilder();        for (int i = 0; i < q; i++) {            String[] parts = br.readLine().trim().split(" ");            if (parts[0].equals("add")) {                // merge: 없으면 넣고, 있으면 더한 값을 넣는다. 넣은 값을 돌려준다                int count = tags.merge(parts[1], Integer.parseInt(parts[2]), Integer::sum);                sb.append(count).append('\n');            } else {                String a = parts[1];                String b = parts[2];                if (!tags.containsKey(a)) {                    sb.append(0).append('\n');                } else if (a.equals(b)) {                    sb.append(tags.get(a)).append('\n');                } else {                    int moved = tags.remove(a); // 지우면서 값을 돌려받는다                    int count = tags.merge(b, moved, Integer::sum);                    sb.append(count).append('\n');                }            }        }        sb.append(tags.size()).append('\n');        System.out.print(sb);    }}
C#Main.cs · 48 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        int q = int.Parse(Console.ReadLine().Trim());        var tags = new Dictionary<string, int>();        StringBuilder sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            string[] parts = Console.ReadLine().Trim().Split(' ');            if (parts[0] == "add")            {                // 없는 열쇠에 +=를 하면 예외가 난다: TryGetValue로 먼저 꺼낸다(없으면 0)                tags.TryGetValue(parts[1], out int count);                count += int.Parse(parts[2]);                tags[parts[1]] = count;                sb.Append(count).Append('\n');            }            else            {                string a = parts[1];                string b = parts[2];                if (!tags.ContainsKey(a))                {                    sb.Append(0).Append('\n');                }                else if (a == b)                {                    sb.Append(tags[a]).Append('\n');                }                else                {                    tags.Remove(a, out int moved); // 지우면서 값을 돌려받는다                    tags.TryGetValue(b, out int count);                    count += moved;                    tags[b] = count;                    sb.Append(count).Append('\n');                }            }        }        sb.Append(tags.Count).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 39 行
#include <iostream>#include <string>#include <unordered_map>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int q;    cin >> q;    unordered_map<string, int> tags;    for (int i = 0; i < q; i++) {        string op;        cin >> op;        if (op == "add") {            string t;            int k;            cin >> t >> k;            tags[t] += k; // []는 없는 열쇠를 0으로 만들어 준다            cout << tags[t] << '\n';        } else {            string a, b;            cin >> a >> b;            auto it = tags.find(a); // find는 열쇠를 만들지 않는다            if (it == tags.end()) {                cout << 0 << '\n';            } else if (a == b) {                cout << it->second << '\n';            } else {                int moved = it->second;                tags.erase(it);                tags[b] += moved;                cout << tags[b] << '\n';            }        }    }    cout << tags.size() << '\n';    return 0;}
Cmain.c · 62 行
#include <stdio.h>#include <string.h> /* C에는 사전이 없다: 이름 배열과 수 배열을 나란히 두고 앞에서부터 찾는다 */static char names[2000][11];static int counts[2000];static int size = 0; static int find(const char *t) {    for (int i = 0; i < size; i++) {        if (strcmp(names[i], t) == 0) return i;    }    return -1;} /* 없으면 0장으로 새로 만들고 그 자리를 돌려준다 */static int find_or_add(const char *t) {    int i = find(t);    if (i < 0) {        i = size++;        strcpy(names[i], t);        counts[i] = 0;    }    return i;} int main(void) {    int q;    if (scanf("%d", &q) != 1) return 1;    for (int i = 0; i < q; i++) {        char op[8], a[11], b[11];        if (scanf("%7s", op) != 1) return 1;        if (strcmp(op, "add") == 0) {            int k;            if (scanf("%10s %d", a, &k) != 2) return 1;            int at = find_or_add(a);            counts[at] += k;            printf("%d\n", counts[at]);        } else {            if (scanf("%10s %10s", a, b) != 2) return 1;            int from = find(a);            if (from < 0) {                printf("0\n");            } else if (strcmp(a, b) == 0) {                printf("%d\n", counts[from]);            } else {                int moved = counts[from];                /* 지우기: 마지막 칸을 빈 자리로 옮긴다 */                size--;                if (from != size) {                    strcpy(names[from], names[size]);                    counts[from] = counts[size];                }                int to = find_or_add(b);                counts[to] += moved;                printf("%d\n", counts[to]);            }        }    }    printf("%d\n", size);    return 0;}
Craykonmain.cray · 42 行
명령수 = 控制台.读行().转数字();꼬리표들 = 字典();循环 (i = 0; i < 명령수; i++){    칸들 = 控制台.读行().去空白().分割();    如果 (칸들[0] == "add")    {        이름 = 칸들[1];        // 없는 열쇠를 읽으면 멈춘다: 있는지 먼저 본다        如果 (!꼬리표들.包含(이름))        {            꼬리표들[이름] = 0;        }        꼬리표들[이름] += 칸들[2].转数字();        控制台.输出(꼬리표들[이름]);    }    否则    {        원래 = 칸들[1];        새것 = 칸들[2];        如果 (!꼬리표들.包含(원래))        {            控制台.输出(0);        }        否则 如果 (원래 == 새것)        {            控制台.输出(꼬리표들[원래]);        }        否则        {            옮길수 = 꼬리표들[원래]; // 빼기는 값이 아니라 참/거짓을 돌려준다            꼬리표들.移除(원래);            如果 (!꼬리표들.包含(새것))            {                꼬리표들[새것] = 0;            }            꼬리표들[새것] += 옮길수;            控制台.输出(꼬리표들[새것]);        }    }}控制台.输出(꼬리표들.数量);