模拟一个名牌指向整数列表的小账本。 一个名字总是指向一个列表,多个名字可以一起指向同一个列表。 第一行给出命令数 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()/标准输入传入(运行中不会等待输入)。
$ 就绪
请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。
其他语言怎么写?查看各语言题解 · 8 种语言题解
本题接受全部 8 种语言,但得到“指向”的方法各不相同。 Python 的 list、JavaScript/TypeScript 的数组、Java 的 ArrayList、C# 的 List<int> 和 Craykon 的列表都是引用,放进字典或赋值只是多了一个指向同一列表的东西。 所以 let 就是普通赋值,只有 copy 要特意复制:Python a.copy()、a[:]、list(a),JS/TS [...a]、a.slice(),Java new ArrayList<>(a),C# new List<int>(a),Craykon a.子列表(0, 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 的 == 比较内容),Craykon 列表的 == 问的是是否为同一个列表。 四个陷阱:① 在 let 中复制,之后放入的数只在一边可见。 ② 在 copy 中不复制,两个名字会一起改同一个列表。 ③ 按内容比较两个空列表会得到相同。 ④ 把 let x y 写成“用 y 的内容覆盖 x 所指列表的内容”,会连同与 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.连接(" ")); } 跳出; }}