{ }codelper
值类型与引用类型P0382

要么全出、要么一个不出的自动售货机

自动售货机里有 k 种饮料,第 i 种饮料的库存是 s_i 个,价格是 p_i 韩元。
按给出的顺序处理 q 个订单。
一个订单由投入的钱 c 韩元和选中的 t 个饮料编号组成。
同一个订单里可以多次选同一个编号。
按选择的顺序一个一个取出。
每取出一个,这种饮料的库存减 1,剩下的钱减去它的价格,售货机的销售额加上它的价格。
如果要取的饮料库存为 0,或者剩下的钱比它的价格少,整个订单就被取消。
订单被取消时,库存和销售额回到这个订单开始之前的样子。
对每个订单输出一行:如果全部取出,输出 ok 和剩下的钱;如果被取消,输出 fail 和没能取出的饮料是这个订单里第几个选的。
处理完所有订单后,在一行里输出 1 号到 k 号饮料的库存,在下一行输出销售额。
一开始销售额是 0 韩元。
试着把库存和销售额放进一个结构体,每个订单先复制这个结构体,在副本上试着取出,全部取出后用副本覆盖原来的结构体。

输入
第一行给出 k 和 q,用一个空格隔开。
第二行给出 s_1 到 s_k,第三行给出 p_1 到 p_k,都用一个空格隔开。
接下来 q 行,每行给出 c、t 以及选中的 t 个饮料编号,用一个空格隔开。
1 ≤ k ≤ 8,1 ≤ q ≤ 1000,0 ≤ s_i ≤ 20,1 ≤ p_i ≤ 1000,0 ≤ c ≤ 20000,1 ≤ t ≤ 20,饮料编号在 1 到 k 之间
输出
q + 2 行。
每个订单输出 "ok 剩下的钱" 或 "fail 位置",用一个空格隔开。
位置从 1 开始数。
下一行输出 k 个库存,用一个空格隔开,最后一行输出销售额
样例 1 · stdin
2 3
2 1
300 500
1000 2 1 2
1000 3 1 1 2
500 1 1
stdout
ok 200
fail 2
ok 200
0 0
1100
样例 2 · stdin
1 2
3
400
1000 3 1 1 1
800 2 1 1
stdout
fail 3
ok 0
1
800
main.py
输入(stdin)

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

$ 就绪

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

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

这道题只接受 C、C++ 和 C#。
能自己定义“赋值时数据整个被复制”的值类型结构体的就是这三种语言;Python、JavaScript、TypeScript、Java 的对象和 Craykon 的 字典() 都是引用,赋值之后指向同一个东西。
把售货机的状态(库存数组和销售额)放进一个结构体 Machine,每个订单先用 Machine trial = m; 做一份副本,在副本上试着取出。
全部成功就用 m = trial; 覆盖回去,被取消就只要丢掉 trial,所以不需要另外写恢复的代码。
结构体赋值复制到哪一层,取决于成员的类型。
C 会把结构体里的数组 int stock[8] 连元素一起复制(数组之间不能用 = 赋值,但放在结构体里就会一起被复制)。
C++ 的 vector<int> stock 成员也是连元素一起复制。
C# 的 struct 复制的是字段的值,而 int[] 字段的值是指向数组的引用。
所以 C# 的解法在 Copy() 方法里用 (int[])Stock.Clone() 新建一个数组。
陷阱一:在 C# 里只写 Machine trial = m;,Sales 是各自的,但 Stock 数组是共用的。
第一个例子里第二个订单被取消后 1 号饮料的库存仍然是 0,于是第三个订单变成 fail 1,销售额是 800。
陷阱二:在 C 里如果把库存写成 int *stock(用 malloc 申请的地方),就和 C# 一样只复制地址,得到同样的错误答案。
陷阱三:C 的 Machine *trial = &m; 和 C++ 的 Machine& trial = m; 不是复制,而是指向 m 的另一个名字。
取消之后库存和销售额都保持改动后的样子,所以第一个例子的第三个订单变成 fail 1。
陷阱四:如果取出的函数像 bool take(Machine t, int x, int money) 这样按值接收结构体,改变的只是函数里的副本。
第一个例子里库存不会减少,于是第二个订单变成 fail 3,最后输出 2 1 和 0。
要原地修改,C 用 Machine *t 接收并以 take(&trial, …) 调用,C++ 用 Machine& t 接收,C# 则写成结构体的方法或者用 ref Machine t 接收。

C#Main.cs · 66 行
using System;using System.Text; struct Machine{    public int[] Stock; // 배열은 참조다: 구조체를 대입해도 배열 자체는 복사되지 않는다    public int Sales;     // 배열까지 새로 만든 복사본    public Machine Copy()    {        Machine c = this;        c.Stock = (int[])Stock.Clone();        return c;    }     // 메서드는 자기 자신(this)을 제자리에서 바꾼다    public bool Take(int x, int money, int[] price)    {        if (Stock[x] == 0 || money < price[x]) return false;        Stock[x]--;        Sales += price[x];        return true;    }} class Program{    static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int pos = 0;        int k = int.Parse(data[pos++]), q = int.Parse(data[pos++]);        var m = new Machine { Stock = new int[k], Sales = 0 };        var price = new int[k];        for (int i = 0; i < k; i++) m.Stock[i] = int.Parse(data[pos++]);        for (int i = 0; i < k; i++) price[i] = int.Parse(data[pos++]);         var sb = new StringBuilder();        for (int o = 0; o < q; o++)        {            int money = int.Parse(data[pos++]), t = int.Parse(data[pos++]);            Machine trial = m.Copy();            int failed = 0;            for (int j = 0; j < t; j++)            {                int x = int.Parse(data[pos + j]) - 1;                if (!trial.Take(x, money, price)) { failed = j + 1; break; }                money -= price[x];            }            pos += t;            if (failed > 0)            {                sb.Append("fail ").Append(failed).Append('\n');            }            else            {                m = trial;                sb.Append("ok ").Append(money).Append('\n');            }        }         sb.Append(string.Join(" ", m.Stock)).Append('\n').Append(m.Sales).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 53 行
#include <iostream>#include <vector>using namespace std; struct Machine {    vector<int> stock; // vector 멤버는 구조체를 대입할 때 원소까지 복사된다    int sales = 0;}; vector<int> price; // 제자리에서 바꾸려고 참조로 받는다bool take(Machine& t, int x, int money) {    if (t.stock[x] == 0 || money < price[x]) return false;    t.stock[x]--;    t.sales += price[x];    return true;} int main() {    int k, q;    cin >> k >> q;    Machine m;    m.stock.resize(k);    price.resize(k);    for (int& s : m.stock) cin >> s;    for (int& p : price) cin >> p;     for (int o = 0; o < q; o++) {        int money, t;        cin >> money >> t;        vector<int> picks(t);        for (int& x : picks) cin >> x;         Machine trial = m; // 재고와 매출이 모두 복사된다        int failed = 0;        for (int j = 0; j < t; j++) {            int x = picks[j] - 1;            if (!take(trial, x, money)) { failed = j + 1; break; }            money -= price[x];        }        if (failed) {            cout << "fail " << failed << '\n';        } else {            m = trial;            cout << "ok " << money << '\n';        }    }     for (int i = 0; i < k; i++) cout << (i ? " " : "") << m.stock[i];    cout << '\n' << m.sales << '\n';    return 0;}
Cmain.c · 51 行
#include <stdio.h> #define MAXK 8#define MAXT 20 typedef struct {    int stock[MAXK]; // 구조체 안의 배열은 구조체를 대입할 때 통째로 복사된다    int sales;} Machine; int price[MAXK]; // 제자리에서 바꾸려고 포인터로 받는다int take(Machine *t, int x, int money) {    if (t->stock[x] == 0 || money < price[x]) return 0;    t->stock[x]--;    t->sales += price[x];    return 1;} int main(void) {    int k, q;    if (scanf("%d %d", &k, &q) != 2) return 0;    Machine m = {{0}, 0};    for (int i = 0; i < k; i++) scanf("%d", &m.stock[i]);    for (int i = 0; i < k; i++) scanf("%d", &price[i]);     for (int o = 0; o < q; o++) {        int money, t, picks[MAXT];        scanf("%d %d", &money, &t);        for (int j = 0; j < t; j++) scanf("%d", &picks[j]);         Machine trial = m; // 재고 배열과 매출이 모두 복사된다        int failed = 0;        for (int j = 0; j < t; j++) {            int x = picks[j] - 1;            if (!take(&trial, x, money)) { failed = j + 1; break; }            money -= price[x];        }        if (failed) {            printf("fail %d\n", failed);        } else {            m = trial;            printf("ok %d\n", money);        }    }     for (int i = 0; i < k; i++) printf(i ? " %d" : "%d", m.stock[i]);    printf("\n%d\n", m.sales);    return 0;}