Description
石头剪刀布是常见的猜拳游戏:石头胜剪刀,剪刀胜布,布胜石头。如果两个人出拳一样,则不分胜负。在《生活大爆炸》第二季第8集中出现了一种石头剪刀布的升级版游戏。
升级版游戏在传统的石头剪刀布游戏的基础上,增加了两个新手势:
斯波克:《星际迷航》主角之一。
蜥蜴人:《星际迷航》中的反面角色。
这五种手势的胜负关系如表一所示,表中列出的是甲对乙的游戏结果。
现在,小A和小B尝试玩这种升级版的猜拳游戏。已知他们的出拳都是有周期性规律的,但周期长度不一定相等。
例如:如果小A以“石头-布-石头-剪刀-蜥蜴人-斯波克”长度为6的周期出拳,那么他的出拳序列就是“石头-布-石头-剪刀-蜥蜴人-斯波克-石头-布-石头-剪刀-蜥蜴人-斯波克-……”,
而如果小B以“剪刀-石头-布-斯波克-蜥蜴人”长度为5的周期出拳,那么他出拳的序列就是“剪刀-石头-布-斯波克-蜥蜴人-剪刀-石头-布-斯波克-蜥蜴人-……”
已知小A和小B一共进行N次猜拳。每一次赢的人得1分,输的得0分;平局两人都得0分。现请你统计N次猜拳结束之后两人的得分。
Input
第一行包含三个整数:N,NA,NB,分 别 表 示 共 进 行N次猜拳、小A出拳的周期长度,小B出拳的周期长度。数与数之间以一个空格分隔。
第二行包含NA个整数,表示小A出拳的规律,第三行包含NB个整数,表示小B出拳的规律。其中,0表示“剪刀”,1表示“石头”,2表示“布”,3表示“蜥蜴人”, 4表示“斯波克”。数与数之间以一个空格分隔。
Output
输出一行, 包含两个整数,以一个空格分隔,分别表示小A、小B的得分。
Sample Input 1
10 5 6
0 1 2 3 4
0 3 4 2 1 0
Sample Output 1
6 2
Hint
对于100%的数据,0 < N ≤ 200,0 < NA ≤ 200, 0 < NB ≤ 200。
Source
NOIP全国联赛提高组
分析
本题是一道石头剪刀布的改编题,我们根据传统的石头剪刀布的解题方法就能想出思路。但在做的时候我们要注意和传统的剪刀石头布不同的地方,可能就是因为这些改动导致某些传统方法会变得不适用。
首先
本题设置了双方出拳的规律周期,周期的长度和规律的内容都需要输入。自然想到,我们需要一个数组来存放规律周期,数组的长度设为周期长度,通过 for 循环来将规律输入进数组里。
可是这里就有一个问题了,周期长度我们不知道,而数组下标又只能放置常量表达式。这该怎么办呢?这里我们可以使用 'malloc()' 函数来创建动态数组。
int* pa = (int*)malloc(NA * (sizeof(int)));
int* pb = (int*)malloc(NB * (sizeof(int)));
其实这里也可以不用动态数组,在Hint里写有:0 < NA <= 200, 0 < NB <= 200。所以,我们也可以直接设置200个数组,因为NA最大也不会超过200。
int a[200] = { 0 };
int b[200] = { 0 };
(PS:用动态数组后,在VS上运行会报warning:警告 C6385 正在从 "pa" 读取无效数据。
而直接设置200个数组就不会报,我也不知道为什么,也没有发生数组越界的情况,希望能有大牛赏脸解答)
接着再是
本题相比传统的石头剪刀布,新加了两个手势:蜥蜴人,斯波克。
这一点相当重要,可能有些人在这里一瞟就过了,没有去细想这里新加了两个手势会对传统模型产生什么样的改动,认为无非就是多了两个需要比较的变量罢了,没什么大不了的。但如果你是用传统的switch数值算法,那么就会产生一些让你二丈和尚摸不着头脑的结果。
我们先来看看传统的石头剪刀布的解题模型。
传统的石头剪刀布解题模型里有一种解法是将手势数值化,双方比较的结果用数值差来表示,然后根据相减得出的数字通过switch来输出。
传统石头剪刀布的结果图示:
结果 |
剪刀(0) |
石头(1) |
布(2) |
剪刀(0) |
平(0) |
输(-1) |
赢(-2) |
石头(1) |
赢(1) |
平(0) |
输(-1) |
布(2) |
输(2) |
赢(1) |
平(0) |
结果:
赢:-2、1
平:0
输:-1、2
由上图可知,当 A - B = -2 、1 时,A获胜。也就是说,我们只用检查A、B相减后得出的差值,就能判断出胜败结果,这样做可以很方便的将比较过程进行量化而不用一 一判断所有的过程,因此很多人都倾向于使用这种解法。但如果你没有注意到本题新加的两个手势,想都不想地就使用这一解法的话,那就要小心了。
我们来看看在传统的基础上新加两个手势会怎么样。
本题的改版石头剪刀布的图示:
结果(A) |
剪刀(0) |
石头(1) |
布(2) |
蜥蜴人(3) |
斯波克(4) |
剪刀(0) |
平(0) |
输(-1) |
赢(-2) |
赢(-3) |
输(-4) |
石头(1) |
赢(1) |
平(0) |
输(-1) |
赢(-2) |
输(-3) |
布(2) |
输(2) |
赢(1) |
平(0) |
输(-1) |
赢(-2) |
蜥蜴人(3) |
输(3) |
输(2) |
赢(1) |
平(0) |
赢(-1) |
斯波克(4) |
赢(4) |
赢(3) |
输(2) |
输(1) |
平(0) |
结果:
赢:-3、-2、-1、1、3、4
平:0
输:-4、-3、1、2、3
通过上表可知,如果使用传统的剪刀石头布的switch数值算法,将结果用双方数值的差来表示,会发现赢的结果和输的结果里都有 -3、1、3 这三个数字,那么就会导致在你用数值判断的时候,可能会将输的结果判断成赢,赢的结果判断成输,最后发现输出与所想的不一样,但却怎么也看不出来哪里错了。
因此,本题不能选择方便快捷的数值算法,此时选择你们认为麻烦,愚笨,但更为明确清晰,且更容易想到的将所有过程一一比较的穷举法更为妥当。比赛时可没有时间给你思考精明简便的算法,应该果断选取能想到的可靠的算法。
本题穷举法结果图示:
结果 |
剪刀 |
石头 |
布 |
蜥蜴人 |
斯波克 |
赢 |
布、蜥蜴人 |
剪刀、蜥蜴人 |
石头、斯波克 |
布、斯波克 |
剪刀、石头 |
平 |
剪刀 |
石头 |
布 |
蜥蜴人 |
斯波克 |
输 |
石头、斯波克 |
布、斯波克 |
剪刀、蜥蜴人 |
剪刀、石头 |
布、蜥蜴人 |
例如:当 A 是 '剪刀' 时,检查 B 的值,若为 '布' 或者 '蜥蜴人' ,则 A 获胜;若为 '剪刀' ,则为 平局 ;若为 '石头' 或 '斯波克' ,则 B 获胜。
穷举法也有两种写法:
一种是if嵌套
if (pa[x] == 0)
{
if (pb[y] == 2 || pb[y] == 3)
{
sum_A++;
}
else if (pb[y] != 0)
{
sum_B++;
}
}
else if (pa[x] == 1)
{
if (pb[y] == 0 || pb[y] == 3)
{
sum_A++;
}
else if (pb[y] != 1)
{
sum_B++;
}
}
else if (pa[x] == 2)
{
if (pb[y] == 1 || pb[y] == 4)
{
sum_A++;
}
else if (pb[y] != 2)
{
sum_B++;
}
}
else if (pa[x] == 3)
{
if (pb[y] == 2 || pb[y] == 4)
{
sum_A++;
}
else if (pb[y] != 3)
{
sum_B++;
}
}
else if (pa[x] == 4)
{
if (pb[y] == 0 || pb[y] == 1)
{
sum_A++;
}
else if (pb[y] != 4)
{
sum_B++;
}
}
一种是switch嵌套
switch (pa[x])
{
case 0 :
switch (pb[y])
{
case 2:
case 3:
sum_A++;
break;
case 0 :
break;
default:
sum_B++;
break;
}
break;
case 1 :
switch (pb[y])
{
case 0:
case 3:
sum_A++;
break;
case 1:
break;
default:
sum_B++;
break;
}
break;
case 2 :
switch (pb[y])
{
case 1:
case 4:
sum_A++;
break;
case 2:
break;
default:
sum_B++;
break;
}
break;
case 3 :
switch (pb[y])
{
case 2:
case 4:
sum_A++;
break;
case 3:
break;
default:
sum_B++;
break;
}
break;
default :
switch (pb[y])
{
case 0:
case 1:
sum_A++;
break;
case 4:
break;
default:
sum_B++;
break;
}
break;
}
这两种写法并无本质区别,看个人喜好。还有,在写判断条件的时候可以转换一下。例如:在判断 A输(B赢)的局面时,可以将其作为其它项,即:A 既没赢也不是平局。这样可以使代码更简便一点。
另外还有一点
本题用 '0' 表示 “剪刀” , '1' 表示 “石头” , '2' 表示 “布” , '3' 表示 “蜥蜴人” , '4' 表示 “斯波克” 。
所以,可以用枚举来给 '0'、'1' 、'2' 、'3' 、'4' 这五个数字赋予符号名称,这样可以更方便地列举情况,而不会将数字对应的手势混淆。
enum hand { jd, st, bu, xyr, sbk };
完整代码:
#include <stdio.h>
#include <stdlib.h>
enum hand { jd, st, bu, xyr, spk };
int main(void)
{
int N, NA, NB;
int sum_A = 0;
int sum_B = 0;
int x = 0;
int y = 0;
scanf_s("%d %d %d", &N, &NA, &NB);
// 动态数组写法
int* pa = (int*)malloc(NA * (sizeof(int)));
int* pb = (int*)malloc(NB * (sizeof(int)));
/* // 静态数组写法
int pa[200] = { 0 };
int pb[200] = { 0 };
*/
for (int a = 0; a < NA; a++)
{
scanf_s("%d", &pa[a]);
}
for (int b = 0; b < NB; b++)
{
scanf_s("%d", &pb[b]);
}
for (int i = 1; i <= N; i++)
{
/* // if嵌套写法
if (pa[x] == jd)
{
if (pb[y] == bu || pb[y] == xyr)
{
sum_A++;
}
else if (pb[y] != jd)
{
sum_B++;
}
}
else if (pa[x] == st)
{
if (pb[y] == jd || pb[y] == xyr)
{
sum_A++;
}
else if (pb[y] != st)
{
sum_B++;
}
}
else if (pa[x] == bu)
{
if (pb[y] == st || pb[y] == spk)
{
sum_A++;
}
else if (pb[y] != bu)
{
sum_B++;
}
}
else if (pa[x] == xyr)
{
if (pb[y] == bu || pb[y] == spk)
{
sum_A++;
}
else if (pb[y] != xyr)
{
sum_B++;
}
}
else if (pa[x] == spk)
{
if (pb[y] == jd || pb[y] == st)
{
sum_A++;
}
else if (pb[y] != spk)
{
sum_B++;
}
}*/
// switch嵌套写法
switch (pa[x])
{
case jd :
switch (pb[y])
{
case bu:
case xyr:
sum_A++;
break;
case jd :
break;
default:
sum_B++;
break;
}
break;
case st :
switch (pb[y])
{
case jd:
case xyr:
sum_A++;
break;
case st:
break;
default:
sum_B++;
break;
}
break;
case bu :
switch (pb[y])
{
case st:
case spk:
sum_A++;
break;
case bu:
break;
default:
sum_B++;
break;
}
break;
case xyr :
switch (pb[y])
{
case bu:
case spk:
sum_A++;
break;
case xyr:
break;
default:
sum_B++;
break;
}
break;
case spk :
switch (pb[y])
{
case jd:
case st:
sum_A++;
break;
case spk:
break;
default:
sum_B++;
break;
}
break;
default :
break;
}
x++;
y++;
if (x == NA)
{
x = 0;
}
if (y == NB)
{
y = 0;
}
}
printf("%d %d", sum_A, sum_B);
free(pa);
free(pb);
return 0;
}