이름표가 정수 목록을 가리키는 작은 장부를 따라 한다.
이름 하나는 언제나 목록 하나를 가리키고, 여러 이름이 같은 목록 하나를 함께 가리킬 수 있다.
첫 줄에 명령 수 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
오류 목록00F8 ↓ · ⇧F8 ↑
입력(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 syslines = 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.잇기(" ")); } 그만; }}