{ }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()/标准输入传入(运行中不会等待输入)。

$ 就绪

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

其他语言怎么写?查看各语言题解 · 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 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.连接(" "));            }            跳出;    }}