{ }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 を「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.つなぐ(" "));            }            抜ける;    }}