「ACM-ICPC 2018 南京站网络赛 C 题」GDY - 模拟

版权声明:禁止商业用途,如需转载请注明原文出处:https://hyp1231.github.io 或者 https://blog.csdn.net/hyp1231/article/details/82291497

建议访问原文出处,获得更佳浏览体验。
原文出处:https://hyp1231.github.io/2018/09/01/20180901-2018nanjing-online-c/

题意

n 个人玩纸牌游戏,牌堆里初始有 m 张牌。开始每人一次性从牌堆顶抓五张牌。大家依次出牌,直到有一人手中无牌,此人获胜。

游戏流程为:

  • 牌的大小顺序为 [ 3 , 4 , , 12 , 13 , 1 , 2 ] ,从小到大。
  • 第一个人出他手中最小的牌。
  • 每个人要出上一张牌的下一个顺序的牌,如上一张出了 13 ,这张就要出 1
  • 当上一张牌不是 2 的时候,可以出 2 当作任何牌。
  • 一个人无法出牌,就跳过。
  • 当一个人出了牌后,其他人都无法出牌,就从这个人开始一人抓一张牌;之后这个人出一张手里最小的牌。
  • 当牌堆为空时,跳过抓牌操作。

输出游戏结束时,每个人手里的牌面额之和。如果这个人获胜,输出 Winner。

链接

C. GDY

题解

一道题意很复杂的大模拟。

首先规范一下数据储存的形式。开一个二维数组 cnt[i][j] 代表玩家 i 手中花色为 j 的牌的数量,以及 tot[i] 数据记录玩家 i 手中的牌数。之后每次有抓牌 / 出牌操作时,都注意两个数组同时修改。注意每次出牌后,都要判断该玩家手中是否有牌。

数组 stack[] 记录牌堆情况,top 为栈顶指针,当 top == m 时牌堆为空。

抽象出函数 int find_min(int id) ,输入玩家 i d ,返回玩家手中最小的牌。

抽象函数 void draw(int id) 表示从玩家 i d 开始,轮流抓一张牌。注意控制牌堆剩余数量,当牌堆为空时不进行抓牌操作。

在游戏主过程中,记录上一次出的牌,以及上一个出牌的人。值得注意的是,在下面三种情况下,都会出现轮圈转牌的情况(draw 函数):记 n e x t 表示恰好比上一张牌大的牌。

  1. 一圈无人出牌。
  2. n e x t 2
  3. 手中没有 n e x t ,但还有 2

对于 2 3 这两种情况,由于出了一张 2 后,自然无人能管得上你的牌,可以直接跳过这一圈,进行抓牌、出当前最小牌的操作。

更多细节见下方代码实现。

代码

#include <cstdio>
#include <queue>
#include <vector>
#include <functional>
#include <cstring>

const int M = 20010;
const int N = 256;

int order[14] = { 0, 12, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
// order[i] 表示花色 i 的排序位置

int list[14] = { 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 2 };
// list[i] 表示排第 i 的花色

int stack[M];   // 牌堆

int n, m, top;  // top 是栈顶指针,当 top == m 时,牌堆为空
int cnt[N][14]; // cnt[i][j] 表示玩家 i 手中花色为 j 的牌的数量
int tot[N];     // tot[i] 表示玩家 i 手中的牌的数量

void init() {
    int id = 0; top = 0;
    memset(cnt, 0, sizeof(cnt));
    memset(tot, 0, sizeof(tot));
    while (top < m && id < n) {
        ++id;
        for (int i = 0; i < 5 && top < m; ++i) {
            int t = stack[++top];
            ++cnt[id][t];
            ++tot[id];
        }
    }
}   // 初始化变量,及首轮摸牌

int find_min(int id) {
    for (int i = 1; i <= 13; ++i) {
        int u = list[i];
        if (cnt[id][u] != 0) {
            return u;
        }
    }
    return -1;
}   // 返回玩家 id 手中点数最小的牌,-1 表示空

void draw(int id) {
    for (int i = 1; i <= n && top < m; ++i) {
        int u = stack[++top];
        ++cnt[id][u];
        ++tot[id];
        ++id; if (id > n) { id = 1; }
    }
}   // 从玩家 id 开始顺时针抓牌一圈。注意当牌被摸完时跳出循环。

int play() {
    int id = 1;         // 玩家 id
    int last;           // 上一轮出的牌的花色
    int last_player = 1;// 上一个出了牌的玩家 id

    last = find_min(1); // 玩家 1 出手中最小的牌
    --cnt[1][last];
    --tot[1];

    while (1) {
        ++id;   // 下一位玩家
        if (id > n) id = 1;

        if (id == last_player) {
            draw(id);
            int next = find_min(id);
            --cnt[id][next];
            --tot[id];
            if (tot[id] == 0) return id;
            last = next;
            last_player = id;
            continue;
        }       // 说明一圈过后,无人出牌

        if (last == 2) continue;    // 上一张是 2,一定没有更大的牌出

        int next = list[order[last] + 1];       // 找到恰好大 1 点的牌 next
        if (cnt[id][next] > 0 && next != 2) {   // 可以出 next 牌
            --cnt[id][next];
            --tot[id];
            last = next;
            last_player = id;
            if (tot[id] == 0) return id;
        } else if (cnt[id][2] > 0) {            // 可以出一张 2
            --cnt[id][2];
            --tot[id];
            if (tot[id] == 0) return id;
            draw(id);   // 抓一圈牌
            int next = find_min(id);
            --cnt[id][next];
            --tot[id];
            if (tot[id] == 0) return id;
            last = next;
            last_player = id;
        }
    }
    return id;
}   // 游戏主过程,返回胜利者

int main() {
    int T;
    scanf("%d", &T);
    for (int Case = 1; Case <= T; ++Case) {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; ++i) {
            scanf("%d", &stack[i]);
        }   // 输入牌堆

        init();

        int winner = play();

        printf("Case #%d:\n", Case);
        for (int i = 1; i <= n; ++i) {
            if (winner == i) {
                printf("Winner\n");
            } else {
                int sum = 0;
                for (int j = 1; j <= 13; ++j) {
                    sum += j * cnt[i][j];
                }
                printf("%d\n", sum);
            }
        }
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/hyp1231/article/details/82291497
今日推荐