占卜DIY

GitHub跳转原题关系图返回列表

用 13 个双端队列模拟牌堆:放到堆顶用 push_front、抽取堆底用 pop_back,每翻开一张就累加点数计数,最后数 A~Q 里凑满 4 张正面朝上的点数。

OJ: roj

题目 ID: 3022

难度:普及-

标签:模拟队列python

创建: 2026-10-01 10:37

更新: 2026-10-07 13:50

题目描述

一副去掉大小王的扑克共 52 张均分成 13 堆编号 1∼131\sim13,每堆 4 张,第 13 堆为「生命牌堆」、4 张 K 是死神,点数 A=1A=1、2∼92\sim9 取原值、0=10、J=11J=11、Q=12Q=12、K=13K=13,初始全背面朝上。每回合从生命牌堆最上面取一张,翻正面压到点数对应编号堆的最上面,再从该堆最下面取一张继续;抽到 K 就扔掉、生命减一并重新开始。生命从 4 减到 0 后,统计 A∼QA\sim Q 中 4 张牌全部正面朝上的点数个数(K 不计)。输入 13 行,每行 4 个数字或字母表示一堆牌从上到下的牌型(0 代表 10);输出一个整数表示开出的总对数。样例输入 13 行依次为 8 5 A A、K 5 3 2、9 6 0 6、3 4 3 4、3 4 4 5、5 6 7 6、8 7 7 7、9 9 8 8、9 0 0 0、K J J J、Q A Q K、J Q 2 2、A K Q 2,样例输出 9。

思路

照规则模拟:13 个牌堆各用一个双端队列,「压到最上面」是队首插入、「从最下面取」是队尾弹出,两端不能写反。由于点数为 vv 的牌翻开后一定压入 vv 号堆,只需用数组 up[v] 记录点数 vv 被翻开的次数,最后数 up[v]==4 的 v∈{1,…,12}v\in\{1,\dots,12\} 即可,不必追踪具体牌的位置。抽到 K 直接丢弃并结束本回合,总步数是常数。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 10:37
# update_at: 2026-10-01 10:42

import sys
from collections import deque

# 牌面 → 点数:A=1、2~9 原值、0 代表 10、J=11、Q=12、K=13(按这张表逐字加一)。
# 键用 bytes,直接匹配 sys.stdin.buffer 切出来的牌面,省一次解码。
VALUE = {name.encode(): value for value, name in enumerate("A234567890JQK", 1)}

PILE_COUNT = 13  # 13 堆,每堆 4 张
LIVES = 4        # 4 张 K = 4 条命
DEATH = 13       # K 的点数;编号 13 的堆既是生命牌堆,也是点数 13 对应的堆


def solve() -> None:
    data = iter(sys.stdin.buffer.read().split())
    piles: list[deque[int]] = [deque() for _ in range(PILE_COUNT + 1)]
    for i in range(PILE_COUNT):  # 输入顺序即从上到下
        pile_size = 4
        piles[i + 1].extend(VALUE[next(data)] for _ in range(pile_size))

    up = [0] * (PILE_COUNT + 1)  # up[v]:点数 v 已经"翻开并入堆"了多少张
    for _ in range(LIVES):       # 每死一条命就回到第 1 步,重抽生命牌堆顶
        value = piles[DEATH].popleft()    # 第 1 步:抽生命牌堆最上面的一张
        while value != DEATH:             # 抽到 K 则本轮结束,K 被扔掉、不再入堆
            piles[value].appendleft(value)  # 正面朝上压到对应编号堆的最上边
            up[value] += 1
            value = piles[value].pop()      # 再从同一堆最底下抽一张,继续往下走

    print(sum(up[v] == 4 for v in range(1, DEATH)))  # A~Q 中凑齐 4 张正面朝上的点数个数


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 11:13
 * update_at: 2026-10-06 11:13
 */
#include <deque>
#include <iostream>
#include <string>
using namespace std;
typedef long long ll;

deque<ll> piles[14]; // piles[i]:第 i 堆牌,从上到下存点数(队首是堆顶)
ll up[14];           // up[v]:点数 v 被翻开并压入 v 号堆的次数,即正面朝上的 v 的张数

// 把牌面转成点数:A=1、2~9 为原值、0=10、J=11、Q=12、K=13
ll face_to_value(const string &face) {
    if (face == "A") return 1;
    if (face == "0") return 10;
    if (face == "J") return 11;
    if (face == "Q") return 12;
    if (face == "K") return 13;
    return face[0] - '0';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // 读入 13 行,每行 4 张牌,行内顺序即从上到下
    for (ll i = 1; i <= 13; i++) {
        for (ll j = 1; j <= 4; j++) {
            string face;
            cin >> face;
            piles[i].push_back(face_to_value(face));
        }
    }

    // 4 条命:每条命先抽生命牌堆顶,再沿牌链走,直到抽到 K(点数 13)
    for (ll life = 1; life <= 4; life++) {
        ll value = piles[13].front(); // 生命牌堆最上面一张
        piles[13].pop_front();
        while (value != 13) {
            piles[value].push_front(value); // 正面朝上压到 value 号堆顶
            up[value]++;
            ll next_value = piles[value].back(); // 从该堆最下面抽一张
            piles[value].pop_back();
            value = next_value;
        }
    }

    ll answer = 0; // A~Q 中凑满 4 张正面朝上的点数个数(K 不计)
    for (ll v = 1; v <= 12; v++) {
        if (up[v] == 4) answer++;
    }
    cout << answer << '\n';
    return 0;
}