[NOIP2014]生活大爆炸版石头剪刀布

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

把五手势胜负关系预编译成查表,两人按各自周期取模逐轮判胜计分,O(N) 模拟出全部 N 轮得分。

OJ: roj

题目 ID: 3618

难度:入门

标签:入门模拟python

创建: 2026-10-02 10:54

更新: 2026-10-06 15:14

题目描述

甲、乙两人玩升级版石头剪刀布:手势 00 剪刀、11 石头、22 布、33 蜥蜴人、44 斯波克,每种手势恰好胜两种、负两种,每轮胜者得 11 分、平局不得分。两人分别按周期 NAN_A、NBN_B 循环出拳,第 rr 轮(从 00 计)甲出 ar mod NAa_{r \bmod N_A}、乙出 br mod NBb_{r \bmod N_B},共 NN 轮。输入第一行 N,NA,NBN,N_A,N_B,第二、三行分别为两人的周期序列;输出甲、乙两人的总得分。0<N,NA,NB≤2000<N,N_A,N_B\le 200。

样例输入:10 5 6 / 0 1 2 3 4 / 0 3 4 2 1 0,输出 6 2。

思路

把 1010 对胜负关系预存成表,每轮用 r mod NAr \bmod N_A、r mod NBr \bmod N_B 取出双方手势查表判胜,胜者计一分即可。N≤200N\le 200,直接 O(N)O(N) 模拟。

参考代码

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-02 10:54
# update_at: 2026-10-02 10:54

import sys

# 五种手势:0 剪刀,1 石头,2 布,3 蜥蜴人,4 斯波克
# 胜负关系表:(先手, 后手) 表示先手胜后手,共 5 × 2 = 10 对
BEATS: set[tuple[int, int]] = {
    (0, 2), (0, 3),  # 剪刀胜布、蜥蜴人
    (1, 0), (1, 3),  # 石头胜剪刀、蜥蜴人
    (2, 1), (2, 4),  # 布胜石头、斯波克
    (3, 2), (3, 4),  # 蜥蜴人胜布、斯波克
    (4, 0), (4, 1),  # 斯波克胜剪刀、石头
}


def winner(hand_a: int, hand_b: int) -> int:
    """单轮判胜:甲胜返回 1,乙胜返回 -1,平局返回 0。"""
    if (hand_a, hand_b) in BEATS:
        return 1
    if (hand_b, hand_a) in BEATS:
        return -1
    return 0


def tally(rounds: int, seq_a: list[int], seq_b: list[int]) -> tuple[int, int]:
    """打满 rounds 轮后返回 (甲得分, 乙得分),出拳按各自周期循环取。"""
    score_a = score_b = 0
    for r in range(rounds):
        result = winner(seq_a[r % len(seq_a)], seq_b[r % len(seq_b)])
        if result == 1:
            score_a += 1
        elif result == -1:
            score_b += 1
    return score_a, score_b


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    rounds = next(data)  # 共进行 N 次猜拳
    len_a = next(data)   # 小 A 出拳周期长度
    len_b = next(data)   # 小 B 出拳周期长度
    seq_a = [next(data) for _ in range(len_a)]
    seq_b = [next(data) for _ in range(len_b)]

    print(*tally(rounds, seq_a, seq_b))


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 15:14
 * update_at: 2026-10-06 15:14
 */
// main.cpp:升级版石头剪刀布,按各自周期出拳,逐轮判胜并累计得分。
#include <iostream>

typedef long long ll;

const int MAXN = 205;

// 胜负表:beats[i][j] 为 1 表示手势 i 胜手势 j。
// 手势编号:0 剪刀,1 石头,2 布,3 蜥蜴人,4 斯波克。
int beats[5][5];

int seq_a[MAXN]; // 小 A 的出拳周期
int seq_b[MAXN]; // 小 B 的出拳周期

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

    // 五种手势各胜另外两种,共 10 对
    beats[0][2] = 1; beats[0][3] = 1; // 剪刀胜布、蜥蜴人
    beats[1][0] = 1; beats[1][3] = 1; // 石头胜剪刀、蜥蜴人
    beats[2][1] = 1; beats[2][4] = 1; // 布胜石头、斯波克
    beats[3][2] = 1; beats[3][4] = 1; // 蜥蜴人胜布、斯波克
    beats[4][0] = 1; beats[4][1] = 1; // 斯波克胜剪刀、石头

    ll n, len_a, len_b;
    std::cin >> n >> len_a >> len_b;

    for (ll i = 0; i < len_a; i++) {
        std::cin >> seq_a[i];
    }
    for (ll i = 0; i < len_b; i++) {
        std::cin >> seq_b[i];
    }

    ll score_a = 0;
    ll score_b = 0;
    for (ll r = 0; r < n; r++) {
        int hand_a = seq_a[r % len_a]; // 第 r 轮小 A 的手势
        int hand_b = seq_b[r % len_b]; // 第 r 轮小 B 的手势
        if (beats[hand_a][hand_b]) {
            score_a++;
        } else if (beats[hand_b][hand_a]) {
            score_b++;
        }
    }

    std::cout << score_a << ' ' << score_b << std::endl;
    return 0;
}