{ }codelper
참조P0262

같은 목록을 가리키는 이름표

이름표가 정수 목록을 가리키는 작은 장부를 따라 한다. 이름 하나는 언제나 목록 하나를 가리키고, 여러 이름이 같은 목록 하나를 함께 가리킬 수 있다. 첫 줄에 명령 수 q, 다음 q줄에 명령이 하나씩 주어진다. - new x: 빈 목록을 새로 만들고, 이름 x가 그 목록을 가리키게 한다. - let x y: x가 y가 가리키는 바로 그 목록을 가리키게 한다.
목록을 복사하지 않는다. - copy x y: y가 가리키는 목록의 지금 내용을 그대로 담은 새 목록을 만들고, x가 그 새 목록을 가리키게 한다. - push x v: x가 가리키는 목록의 끝에 v를 넣는다.
그 목록을 가리키는 모든 이름에서 바뀐 내용이 보인다. - same x y: x와 y가 같은 목록을 가리키면 yes, 아니면 no를 한 줄에 출력한다.
내용이 같아도 서로 다른 목록이면 no다. - print x: x가 가리키는 목록에 든 수의 개수를 쓰고, 이어서 든 수를 넣은 차례대로 빈칸 하나씩 띄워 한 줄에 출력한다.
비어 있으면 0만 출력한다. new·let·copy로 x가 다른 목록을 가리키게 되어도, x가 전에 가리키던 목록의 내용은 바뀌지 않는다. 그 목록을 함께 가리키던 다른 이름은 계속 그 목록을 가리킨다. x와 y는 같은 이름일 수 있다.
let x x는 아무것도 바꾸지 않고, copy x x는 x가 지금 내용을 복사한 새 목록을 가리키게 한다. x가 처음 나오는 이름이면 new·let·copy가 그 이름을 새로 만든다. 목록을 이름마다 복사해 담지 말고, 목록을 가리키는 것(참조)을 담아 풀어 보자.

입력
1 ≤ q ≤ 2,000.
이름은 영어 소문자·숫자 1~10글자다.
v는 0 이상 1,000,000 이하의 정수다(앞에 0을 붙이지 않음).
let·copy의 y와 push·same·print의 이름은 앞의 new·let·copy로 이미 만들어진 이름이다.
same이나 print가 하나 이상 있고, 출력 전체는 200,000글자 이하다
출력
same과 print마다 한 줄씩, 명령이 나온 차례대로
예시 1 · stdin
12
new a
push a 1
let b a
push b 2
copy c a
same a b
same a c
push c 3
new a
push a 9
print b
print c
stdout
예
아니요
2 1 2
3 1 2 3
예시 2 · stdin
8
new x
new y
same x y
let y x
same y x
copy x x
same x y
print y
stdout
아니요
예
아니요
0
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

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

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

이 문제는 8개 언어를 모두 받아요.
다만 '가리키기'를 얻는 방법이 언어마다 달라요.
Python list, JavaScript·TypeScript 배열, Java ArrayList, C# List<int>, 크레이콘 목록은 모두 참조라서, 사전에 담거나 대입하면 같은 목록을 가리키는 것이 하나 더 생길 뿐이에요.
그래서 let은 그냥 대입이고, copy에서만 일부러 복사해요: Python a.copy()·a[:]·list(a), JS·TS [...a]·a.slice(), Java new ArrayList<>(a), C# new List<int>(a), 크레이콘 a.부분목록(0, a.개수).
반대로 C++ std::vector는 값이라서 names[x] = names[y]가 목록을 통째로 복사해요.
같은 목록을 가리키려면 std::shared_ptr<std::vector<int>>(또는 목록 더미의 번호)를 담아요.
C도 List 구조체를 통째로 담으면 대입이 칸 주소와 개수를 복사해서, 한쪽에서 넣은 수의 개수가 다른 쪽에 보이지 않아요.
List * 주소를 담아요.
'same'은 같은 것인지(정체)를 묻는 비교라서 내용 비교와 헷갈리기 쉬워요.
Python is(==는 내용), Java ==(equals는 내용), C# ReferenceEquals(List의 ==도 참조 비교), JS·TS ===, C·C++ 주소 비교(shared_ptr의 ==는 주소, vector의 ==는 내용), 크레이콘 목록의 ==는 같은 목록인지 물어요.
함정 넷: ① let에서 복사하면 뒤에 넣은 수가 한쪽에만 보여요.
② copy에서 복사하지 않으면 두 이름이 한 목록을 같이 바꿔요.
③ 빈 목록 두 개를 내용으로 비교하면 같다고 나와요.
④ let x y를 'x가 가리키던 목록의 내용을 y의 내용으로 덮어쓰기'로 만들면, x와 함께 그 목록을 가리키던 이름까지 바뀌어요.
다시 가리키기(대입)와 고치기(내용 바꾸기)는 다른 일이에요.

Pythonmain.py · 23 줄
import sys lines = sys.stdin.read().split("\n")q = int(lines[0])names = {}  # 이름 → 그 이름이 가리키는 목록(참조)out = []for line in lines[1 : q + 1]:    p = line.split()    cmd = p[0]    if cmd == "new":        names[p[1]] = []    elif cmd == "let":        names[p[1]] = names[p[2]]  # 같은 목록을 가리킨다(복사하지 않는다)    elif cmd == "copy":        names[p[1]] = names[p[2]].copy()  # 내용만 같은 새 목록    elif cmd == "push":        names[p[1]].append(int(p[2]))    elif cmd == "same":        out.append("예" if names[p[1]] is names[p[2]] else "아니요")  # == 는 내용을 비교한다    else:        a = names[p[1]]        out.append(" ".join(map(str, [len(a)] + a)))print("\n".join(out))
JavaScriptmain.js · 17 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");const q = Number(lines[0]);const names = new Map(); // 이름 → 그 이름이 가리키는 배열(참조)const out = [];for (let i = 1; i <= q; i++) {  const [cmd, x, y] = lines[i].trim().split(" ");  if (cmd === "new") names.set(x, []);  else if (cmd === "let") names.set(x, names.get(y)); // 같은 배열을 가리킨다  else if (cmd === "copy") names.set(x, [...names.get(y)]); // 내용만 같은 새 배열  else if (cmd === "push") names.get(x).push(Number(y));  else if (cmd === "same") out.push(names.get(x) === names.get(y) ? "예" : "아니요"); // 배열의 ===는 같은 것인지 본다  else {    const a = names.get(x);    out.push([a.length, ...a].join(" "));  }}console.log(out.join("\n"));
TypeScriptmain.ts · 20 줄
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const q: number = Number(lines[0]);const names = new Map<string, number[]>(); // 이름 → 그 이름이 가리키는 배열(참조)const out: string[] = [];const get = (name: string): number[] => names.get(name)!;for (let i = 1; i <= q; i++) {  const [cmd, x, y] = lines[i].trim().split(" ");  if (cmd === "new") names.set(x, []);  else if (cmd === "let") names.set(x, get(y)); // 같은 배열을 가리킨다  else if (cmd === "copy") names.set(x, [...get(y)]); // 내용만 같은 새 배열  else if (cmd === "push") get(x).push(Number(y));  else if (cmd === "same") out.push(get(x) === get(y) ? "예" : "아니요"); // 배열의 ===는 같은 것인지 본다  else {    const a = get(x);    out.push([a.length, ...a].join(" "));  }}console.log(out.join("\n"));
JavaMain.java · 32 줄
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map; public class Main {    public static void main(String[] args) throws Exception {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int q = Integer.parseInt(in.readLine().trim());        Map<String, List<Integer>> names = new HashMap<>(); // 이름 → 그 이름이 가리키는 목록(참조)        StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            String[] p = in.readLine().trim().split(" ");            switch (p[0]) {                case "new" -> names.put(p[1], new ArrayList<>());                case "let" -> names.put(p[1], names.get(p[2])); // 같은 목록을 가리킨다                case "copy" -> names.put(p[1], new ArrayList<>(names.get(p[2]))); // 내용만 같은 새 목록                case "push" -> names.get(p[1]).add(Integer.parseInt(p[2]));                case "same" -> out.append(names.get(p[1]) == names.get(p[2]) ? "예" : "아니요").append('\n'); // ==는 같은 객체인지                default -> {                    List<Integer> a = names.get(p[1]);                    out.append(a.size());                    for (int v : a) out.append(' ').append(v);                    out.append('\n');                }            }        }        System.out.print(out);    }}
C#Main.cs · 32 줄
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        int q = int.Parse(Console.ReadLine()!.Trim());        var names = new Dictionary<string, List<int>>(); // 이름 → 그 이름이 가리키는 목록(참조)        var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            string[] p = Console.ReadLine()!.Trim().Split(' ');            switch (p[0])            {                case "new": names[p[1]] = new List<int>(); break;                case "let": names[p[1]] = names[p[2]]; break;                    // 같은 목록을 가리킨다                case "copy": names[p[1]] = new List<int>(names[p[2]]); break;    // 내용만 같은 새 목록                case "push": names[p[1]].Add(int.Parse(p[2])); break;                case "same": sb.Append(ReferenceEquals(names[p[1]], names[p[2]]) ? "예" : "아니요").Append('\n'); break;                default:                    List<int> a = names[p[1]];                    sb.Append(a.Count);                    foreach (int v in a) sb.Append(' ').Append(v);                    sb.Append('\n');                    break;            }        }        Console.Write(sb);    }}
C++main.cpp · 40 줄
#include <iostream>#include <map>#include <memory>#include <string>#include <vector> using List = std::vector<int>; int main() {    std::ios::sync_with_stdio(false);    int q;    std::cin >> q;    // vector를 그대로 담으면 대입할 때마다 통째로 복사된다. shared_ptr를 담아 같은 목록을 가리키게 한다    std::map<std::string, std::shared_ptr<List>> names;    for (int i = 0; i < q; i++) {        std::string cmd, x, y;        std::cin >> cmd >> x;        if (cmd == "new") {            names[x] = std::make_shared<List>();        } else if (cmd == "let") {            std::cin >> y;            names[x] = names[y]; // 포인터만 복사: 같은 목록        } else if (cmd == "copy") {            std::cin >> y;            names[x] = std::make_shared<List>(*names[y]); // 내용을 복사한 새 목록        } else if (cmd == "push") {            int v;            std::cin >> v;            names[x]->push_back(v);        } else if (cmd == "same") {            std::cin >> y;            std::cout << (names[x] == names[y] ? "예" : "아니요") << '\n'; // shared_ptr의 ==는 주소 비교        } else {            const List& a = *names[x];            std::cout << a.size();            for (int v : a) std::cout << ' ' << v;            std::cout << '\n';        }    }}
Cmain.c · 71 줄
#include <stdio.h>#include <stdlib.h>#include <string.h> typedef struct {    int *items;    int len, cap;} List; static List *new_list(void) {    List *l = malloc(sizeof *l);    l->len = 0;    l->cap = 4;    l->items = malloc(sizeof(int) * l->cap);    return l;} static void push(List *l, int v) {    if (l->len == l->cap) {        l->cap *= 2;        l->items = realloc(l->items, sizeof(int) * l->cap);    }    l->items[l->len++] = v;} /* 이름표: 이름마다 목록의 주소(List *)를 담는다. 구조체를 통째로 담으면 복사본이 된다 */static char names[2000][11];static List *refs[2000];static int count = 0; static int find(const char *name) {    for (int i = 0; i < count; i++)        if (strcmp(names[i], name) == 0) return i;    strcpy(names[count], name);    refs[count] = NULL;    return count++;} int main(void) {    int q;    scanf("%d", &q);    for (int i = 0; i < q; i++) {        char cmd[8], x[11], y[11];        scanf("%7s %10s", cmd, x);        int a = find(x);        if (strcmp(cmd, "new") == 0) {            refs[a] = new_list();        } else if (strcmp(cmd, "let") == 0) {            scanf("%10s", y);            refs[a] = refs[find(y)]; /* 주소만 옮긴다: 같은 목록 */        } else if (strcmp(cmd, "copy") == 0) {            scanf("%10s", y);            List *src = refs[find(y)], *l = new_list();            for (int k = 0; k < src->len; k++) push(l, src->items[k]);            refs[a] = l;        } else if (strcmp(cmd, "push") == 0) {            int v;            scanf("%d", &v);            push(refs[a], v);        } else if (strcmp(cmd, "same") == 0) {            scanf("%10s", y);            puts(refs[a] == refs[find(y)] ? "예" : "아니요"); /* 주소가 같으면 같은 목록 */        } else {            List *l = refs[a];            printf("%d", l->len);            for (int k = 0; k < l->len; k++) printf(" %d", l->items[k]);            putchar('\n');        }    }    return 0;}
Craykonmain.cray · 38 줄
q = 콘솔.줄읽기().수로변환();이름표 = 사전(); // 이름 → 그 이름이 가리키는 목록. 목록은 참조라서 담아도 복사되지 않는다반복 (i = 0; i < q; i++){    p = 콘솔.줄읽기().공백제거().나누기();    x = p[1];    선택 (p[0])    {        경우 "new":            이름표[x] = [];            그만;        경우 "let":            이름표[x] = 이름표[p[2]]; // 같은 목록을 가리킨다            그만;        경우 "copy":            원본 = 이름표[p[2]];            이름표[x] = 원본.부분목록(0, 원본.개수); // 내용만 같은 새 목록            그만;        경우 "push":            이름표[x].넣기(p[2].수로변환());            그만;        경우 "same":            // 목록끼리 ==는 같은 목록인지 묻는다(내용 비교가 아니다)            콘솔.쓰기(이름표[x] == 이름표[p[2]] ? "예" : "아니요");            그만;        그밖에:            a = 이름표[x];            만약 (a.개수 == 0)            {                콘솔.쓰기("0");            }            아니면            {                콘솔.쓰기(a.개수 + " " + a.잇기(" "));            }            그만;    }}