{ }codelper
值类型与引用类型引用记录P0159

被复制的冒险者与共用的金库

有 n 名冒险者和 t 支队伍。
每名冒险者有坐标 (x, y),属于一支队伍。
每支队伍有一个金库,同一队伍的冒险者共用那一个金库。
第一行给出 n 和 t,第二行给出 1 号到 t 号队伍的初始金额共 t 个。
接下来 n 行以“x y 队伍号”给出 1 号到 n 号冒险者。
然后给出命令数 q,接下来 q 行每行一个命令。
- move i dx dy:i 号冒险者在 x 方向移动 dx,在 y 方向移动 dy。
- earn i g:往 i 号冒险者所属队伍的金库里放入 g。
- clone i j:把 i 号冒险者变成 j 号冒险者的复制品。
坐标变成 j 当前的坐标,队伍变成 j 的队伍。
之后两人的坐标各自移动,但因为同队,金库是共用的。
- leave i:i 号冒险者离开当前队伍,独自组建一支金库为 0 的新队伍。
原队伍的金库保持不变;即使 i 原本是队里唯一的人,新队伍的金库也是 0。
i 和 j 可能相同(此时 clone 不改变任何东西)。
按顺序执行完命令后,按一行一个输出 1 号到 n 号冒险者的“x y 金库金额”。
金库金额是该冒险者所属队伍金库里的金额。
试着把冒险者写成以坐标结构体(值)和指向队伍的东西(C 指针、C++ shared_ptr、C# 类引用)为成员的结构体,并用一次结构体赋值完成 clone。

输入
1 ≤ n ≤ 1,000,1 ≤ t ≤ 1,000,1 ≤ q ≤ 10,000。
初始金额为 0 以上 1,000,000 以下,g 为 1 以上 1,000 以下,初始坐标和 dx、dy 为 -1,000 以上 1,000 以下的整数。
1 ≤ 队伍号 ≤ t,1 ≤ i, j ≤ n
输出
n 行:每名冒险者“x y 金库金额”
样例 1 · stdin
3 2
10 20
0 0 1
5 5 1
9 9 2
5
earn 1 3
clone 3 1
move 3 1 1
earn 3 100
leave 2
stdout
0 0 113
5 5 0
1 1 113
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

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

其他语言怎么写?查看各语言题解 · 3 种语言题解

这道题只接受 C、C++、C#。
能在赋值时整体复制的值类型结构体里同时放一个指向别处的成员,只有这三种语言。
Python、JavaScript、TypeScript、Java 的对象和 Craykon 的 字典()都是引用,无法用一次赋值分出“坐标复制、队伍共享”。
关键在于结构体赋值是浅复制。
值成员(坐标)被重新复制,而指向型成员只复制地址(引用),于是指向同一个东西。
C 用 Team *team,C++ 用 std::shared_ptr<Team> team(复制后指向同一个 Team,最后一个消失时才删除),C# 用 struct Adventurer 里的 class Team 字段来承担这个角色。
五个陷阱:① C# 把 Point 写成 class,clone 之后两人共用一个坐标,一个移动两个都动。
② C# 把 Team 写成 struct,clone 时金库被复制,之后放入的钱只进了一方。
③ C# 把 Adventurer 写成 class,a[i] = a[j] 会让两个格子变成同一个对象。
④ C、C++ 用 Team team; 按值存放队伍,连一开始就同队的冒险者也各有各的金库。
⑤ C++ 用 Adventurer me = a[i - 1]; 取出时只改了副本。
要用 Adventurer &me。
C# 若用 List<Adventurer>,list[i].Pos.X += dx 会编译报错(CS1612),所以要用格子本身是变量的数组 Adventurer[]。

C#Main.cs · 67 行
using System;using System.Text; struct Point   // 값 형식: 대입하면 X, Y가 복사된다{    public int X, Y;} class Team     // 참조 형식: 대입하면 같은 금고를 함께 가리킨다{    public int Gold;} struct Adventurer  // 값 형식 안에 값(Pos)과 참조(Team)가 함께 있다{    public Point Pos;    public Team Team;} class Program{    static void Main()    {        var tokens = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int p = 0;        int Next() => int.Parse(tokens[p++]);         int n = Next(), t = Next();        var teams = new Team[t];        for (int k = 0; k < t; k++) teams[k] = new Team { Gold = Next() };         var a = new Adventurer[n];  // List<Adventurer>가 아니라 배열: a[i].Pos.X += dx가 칸 자체를 바꾼다        for (int i = 0; i < n; i++)        {            a[i].Pos.X = Next();            a[i].Pos.Y = Next();            a[i].Team = teams[Next() - 1];        }         int q = Next();        for (int c = 0; c < q; c++)        {            string cmd = tokens[p++];            int i = Next() - 1;            switch (cmd)            {                case "move":                    a[i].Pos.X += Next();                    a[i].Pos.Y += Next();                    break;                case "earn":                    a[i].Team.Gold += Next();                    break;                case "clone":                    a[i] = a[Next() - 1];  // Pos는 복사되고, Team은 참조만 복사된다                    break;                default:  // leave                    a[i].Team = new Team();                    break;            }        }         var sb = new StringBuilder();        foreach (var ad in a) sb.Append($"{ad.Pos.X} {ad.Pos.Y} {ad.Team.Gold}\n");        Console.Write(sb);    }}
C++main.cpp · 60 行
#include <iostream>#include <memory>#include <string>#include <vector> struct Point {  // 값: 대입하면 복사된다    int x = 0, y = 0;}; struct Team {    int gold = 0;}; // 모험가는 좌표를 직접 담고(값), 팀은 shared_ptr로 함께 가리킨다struct Adventurer {    Point pos;    std::shared_ptr<Team> team;}; int main() {    std::ios::sync_with_stdio(false);    int n, t;    std::cin >> n >> t;    std::vector<std::shared_ptr<Team>> teams(t);    for (auto &tm : teams) {        tm = std::make_shared<Team>();        std::cin >> tm->gold;    }    std::vector<Adventurer> a(n);    for (auto &ad : a) {        int k;        std::cin >> ad.pos.x >> ad.pos.y >> k;        ad.team = teams[k - 1];    }    int q;    std::cin >> q;    for (int c = 0; c < q; c++) {        std::string cmd;        int i;        std::cin >> cmd >> i;        Adventurer &me = a[i - 1];  // 참조로 받아야 배열의 모험가가 바뀐다        if (cmd == "move") {            int dx, dy;            std::cin >> dx >> dy;            me.pos.x += dx;            me.pos.y += dy;        } else if (cmd == "earn") {            int g;            std::cin >> g;            me.team->gold += g;        } else if (cmd == "clone") {            int j;            std::cin >> j;            me = a[j - 1];  // pos는 복사, shared_ptr는 같은 Team을 함께 가리키게 복사        } else {            me.team = std::make_shared<Team>();  // 새 팀(금고 0)        }    }    for (const auto &ad : a) std::cout << ad.pos.x << ' ' << ad.pos.y << ' ' << ad.team->gold << '\n';}
Cmain.c · 59 行
#include <stdio.h>#include <stdlib.h>#include <string.h> typedef struct { int x, y; } Point;   /* 값: 대입하면 복사된다 */typedef struct { int gold; } Team; /* 모험가는 좌표를 직접 담고(값), 팀은 주소로 가리킨다(같은 팀이면 같은 주소) */typedef struct {    Point pos;    Team *team;} Adventurer; int main(void) {    int n, t, q;    scanf("%d %d", &n, &t);    /* 팀 자리: 처음 t개 + leave 때마다 하나씩(많아야 q개) */    int cap = t + 10000;    Team *teams = malloc((size_t)cap * sizeof *teams);    int used = t;    for (int k = 0; k < t; k++) scanf("%d", &teams[k].gold);     Adventurer *a = malloc((size_t)n * sizeof *a);    for (int i = 0; i < n; i++) {        int k;        scanf("%d %d %d", &a[i].pos.x, &a[i].pos.y, &k);        a[i].team = &teams[k - 1];    }     scanf("%d", &q);    for (int c = 0; c < q; c++) {        char cmd[8];        int i;        scanf("%7s %d", cmd, &i);        Adventurer *me = &a[i - 1];        if (strcmp(cmd, "move") == 0) {            int dx, dy;            scanf("%d %d", &dx, &dy);            me->pos.x += dx;            me->pos.y += dy;        } else if (strcmp(cmd, "earn") == 0) {            int g;            scanf("%d", &g);            me->team->gold += g;          /* 같은 팀 모두가 보는 금고 */        } else if (strcmp(cmd, "clone") == 0) {            int j;            scanf("%d", &j);            *me = a[j - 1];               /* 구조체 대입: pos는 복사, team은 주소만 복사 */        } else {                          /* leave: 새 팀을 만들어 옮긴다 */            teams[used].gold = 0;            me->team = &teams[used++];        }    }     for (int i = 0; i < n; i++) printf("%d %d %d\n", a[i].pos.x, a[i].pos.y, a[i].team->gold);    free(a);    free(teams);    return 0;}