[NOIP 2014 提高组] 生活大爆炸版石头剪刀布 的题解
记住只在没有思路时使用题解,不要从它复制粘贴代码。请尊重题目和题解的作者。
在解题之前提交题解的代码会导致封禁。
在解题之前提交题解的代码会导致封禁。
作者:
概述
本题要求按照双方给定的出拳周期模拟 局升级版石头剪刀布,分别统计小 A 与小 B 的得分。核心解法是先把题面表中五种手势的胜负关系固化成一张
的常量表,再按各自周期取模逐局查表累加。
分析
核心观察
每一局的胜负只由双方该局出的手势决定,与之前的局面无关,因此 局的得分可以逐局独立累加。双方手势的组合一共只有
种,把这
种组合的胜负预先存成一张表,单局就退化为一次数组访问。
思路
用 记小 A 的周期,用
记小 B 的周期。两个周期各自循环,故第
局(从
数起)两人出的手势为
两者的周期长度不一定相等,整个出拳序列的最短公共周期是 ,但直接模拟
局已经足够快,无需利用这一点。
令 表示甲出
、乙出
时甲的得分,赢为
,平或负为
,其取值就是题面表中的每一格。第
局中甲是小 A、乙是小 B,故小 A 得
分、小 B 得
分。对任意两个手势
,必有一方获胜而另一方落败,因此
;题面表中留白的下三角正是靠这一关系补全的。当
时两式都取
,恰好对应平局时两人都不得分,无需特判。
朴素做法是每局现场判断双方手势的克制关系,需要常数次比较,总复杂度同样是 ;查表把单局开销压缩到一次数组访问,同时避免了现场判断写错方向。
具体示例
以样例 (
,
,
)为例。小 A 的周期是
0 1 2 3 4,即 剪刀、石头、布、蜥蜴人、斯波克;小 B 的周期是 0 3 4 2 1 0。逐局查表的结果如下。
| 局数 | 小 A | 小 B | 结果 | 小 A 得分 | 小 B 得分 |
|---|---|---|---|---|---|
剪刀 |
剪刀 |
平 | |||
石头 |
蜥蜴人 |
小 A 赢 | |||
布 |
斯波克 |
小 A 赢 | |||
蜥蜴人 |
布 |
小 A 赢 | |||
斯波克 |
石头 |
小 A 赢 | |||
剪刀 |
剪刀 |
平 | |||
石头 |
剪刀 |
小 A 赢 | |||
布 |
蜥蜴人 |
小 B 赢 | |||
蜥蜴人 |
斯波克 |
小 A 赢 | |||
斯波克 |
布 |
小 B 赢 |
前 局小 A 只在第
局与对手打平,其余
局全胜,比分来到
;第
、
局小 A 又拿
分,第
局小 B 拿到本场第
分,第
局小 A 得分到
,第
局小 B 再得
分。终局比分
,与样例输出一致。
算法步骤
- 把题面表中的胜负关系写成常量二维数组
beat,beat[x][y]取表示甲出
、乙出
时甲赢,取
表示平局或甲输;留白的下三角按
补出。
- 读入局数
n与两个周期长度na、nb。 - 读入小 A 的周期数组
a[0 .. na - 1]与小 B 的周期数组b[0 .. nb - 1]。 - 令小 A 的得分
scoreA与小 B 的得分scoreB的初值为。
- 对每一局下标
i()取
x为a[i % na]、y为b[i % nb],把scoreA加上beat[x][y],把scoreB加上beat[y][x]。 - 输出
scoreA与scoreB,两者之间用一个空格分隔。
复杂度分析
- 时间:
。胜负表是常量,每局只做两次取模、两次数组访问与两次加法。
- 空间:
,只需保存两个周期数组,胜负表只占
个整数。
实现注意事项
- 胜负表必须与题面表一逐格对齐。表中下三角留白,非平局的另一格由
补出;凭“五种手势大致怎么克制”的印象填写容易出错,例如
石头对斯波克是输而不是赢。 - 下标从
开始,第
局对应
与
,直接用
i对周期长度取模即可。 - 两人得分分别用
beat[x][y]与beat[y][x]累加,方向不能写反,也不能对同一格重复累加。 - 平局时两人都不得分,而表中平局格的值已是
,不需要额外特判。
、
、
都不超过
,单方得分不超过
,
int足够。- 输入共三行,规模很小,用
cin或一次性读入后切分即可,无需额外优化。 - 输出两项之间是一个空格,行末换行。
源代码
#include <iostream>
#include <vector>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
// beat[i][j] = 1 表示甲出 i、乙出 j 时甲获胜,0 表示甲平或负
static const int beat[5][5] = {
{0, 0, 1, 1, 0}, // 剪刀
{1, 0, 0, 1, 0}, // 石头
{0, 1, 0, 0, 1}, // 布
{0, 0, 1, 0, 1}, // 蜥蜴人
{1, 1, 0, 0, 0}, // 斯波克
};
int n = 0, na = 0, nb = 0;
if (!(std::cin >> n >> na >> nb)) return 0;
std::vector<int> a(na), b(nb);
for (int i = 0; i < na; ++i) std::cin >> a[i];
for (int i = 0; i < nb; ++i) std::cin >> b[i];
int scoreA = 0, scoreB = 0;
for (int i = 0; i < n; ++i) {
const int x = a[i % na];
const int y = b[i % nb];
scoreA += beat[x][y];
scoreB += beat[y][x];
}
std::cout << scoreA << ' ' << scoreB << '\n';
return 0;
}
import sys
# BEAT[i][j] == 1 表示甲出 i、乙出 j 时甲获胜,0 表示甲平或负
BEAT = (
(0, 0, 1, 1, 0), # 剪刀
(1, 0, 0, 1, 0), # 石头
(0, 1, 0, 0, 1), # 布
(0, 0, 1, 0, 1), # 蜥蜴人
(1, 1, 0, 0, 0), # 斯波克
)
def main():
data = sys.stdin.buffer.read().split()
if not data:
return
n, na, nb = int(data[0]), int(data[1]), int(data[2])
a = [int(v) for v in data[3:3 + na]]
b = [int(v) for v in data[3 + na:3 + na + nb]]
score_a = 0
score_b = 0
for i in range(n):
x = a[i % na]
y = b[i % nb]
score_a += BEAT[x][y]
score_b += BEAT[y][x]
sys.stdout.write("%d %d\n" % (score_a, score_b))
if __name__ == "__main__":
main()
评论