{ }codelper
값 타입과 참조 타입참조묶음 자료P0159

복제된 모험가와 함께 쓰는 금고

모험가 n명과 팀 t개가 있다. 모험가마다 좌표 (x, y)를 가지고, 팀 하나에 속한다. 팀마다 금고가 하나 있고, 같은 팀의 모험가는 모두 그 금고 하나를 함께 쓴다. 첫 줄에 n과 t, 둘째 줄에 1번부터 t번 팀의 처음 금액 t개가 주어진다. 다음 n줄에 1번부터 n번 모험가가 "x y 팀번호"로 주어진다. 이어서 명령 수 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인 새 팀을 혼자 만든다.
원래 팀의 금고는 그대로 남고, 지금 팀에 혼자였어도 새 팀의 금고는 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()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 3개 언어 풀이

이 문제는 C·C++·C#만 받아요.
대입하면 통째로 복사되는 값 타입 구조체 안에 다른 것을 가리키는 멤버를 함께 둘 수 있는 게 이 셋이에요.
Python·JavaScript·TypeScript·Java의 객체와 크레이콘의 사전()은 모두 참조라서 '좌표는 복사, 팀은 공유'를 대입 한 번으로 나눌 수 없어요.
핵심은 구조체 대입이 얕은 복사라는 점이에요.
값 멤버(좌표)는 새로 복사되고, 가리키는 멤버는 주소(참조)만 복사되어 같은 것을 함께 가리켜요.
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;}