{ }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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 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에는 사전이 없어서 이름 배열과 수 배열을 나란히 두고 앞에서부터 찾아요. 크레이콘은 빼기가 값이 아니라 참/거짓을 돌려줘서 먼저 읽어 두고, 없는 열쇠는 읽을 수 없으니 있나로 확인해 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;            }            꼬리표들[새것] += 옮길수;            콘솔.쓰기(꼬리표들[새것]);        }    }}콘솔.쓰기(꼬리표들.개수);