历届试题 高僧斗法
时间限制:1.0s 内存限制:256.0MB
问题描述
古时丧葬活动中经常请高僧做法事。仪式结束后,有时会有“高僧斗法”的趣味节目,以舒缓压抑的气氛。
节目大略步骤为:先用粮食(一般是稻米)在地上“画”出若干级台阶(表示N级浮屠)。又有若干小和尚随机地“站”在某个台阶上。最高一级台阶必须站人,其它任意。(如图1所示)
两位参加游戏的法师分别指挥某个小和尚向上走任意多级的台阶,但会被站在高级台阶上的小和尚阻挡,不能越过。两个小和尚也不能站在同一台阶,也不能向低级台阶移动。
两法师轮流发出指令,最后所有小和尚必然会都挤在高段台阶,再也不能向上移动。轮到哪个法师指挥时无法继续移动,则游戏结束,该法师认输。
对于已知的台阶数和小和尚的分布位置,请你计算先发指令的法师该如何决策才能保证胜出。
输入格式
输入数据为一行用空格分开的N个整数,表示小和尚的位置。台阶序号从1算起,所以最后一个小和尚的位置即是台阶的总数。(N<100, 台阶总数<1000)
输出格式
输出为一行用空格分开的两个整数: A B, 表示把A位置的小和尚移动到B位置。若有多个解,输出A值较小的解,若无解则输出-1。
样例输入
1 5 9
样例输出
1 4
样例输入
1 5 8 10
样例输出
1 3
【解题思路】
因为这一题第一次接触博弈,先小小总结一下三种基本博弈
一、巴什博奕
只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜。当n和m取值为多少时,先手必胜?如果n=(m+1)r+s,(r为任意自然数,1 ≤ s≤m那么先取者要拿走s个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。
二、威佐夫博弈
有两堆各若干个物品,两个人轮流从某一堆取任意个或同时从两堆中取同样多个物品,规定每次至少取一个,多者不限,最后取光者得胜。
我们用(ai,bi)(ai ≤ bi ,i=0,1,2,...,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲就输了,这种局势我们称为奇异局势,我们例举一下前几个奇异局势:(0,0) (1,2) (3,5) (4,7) (6,10) (8,13) (9,15) (11,18) (12,20)
只要满足(int)((b-a)*(sqrt(5)+ 1) / 2 )==a,此时(a,b)就是先手必输局势,否则先手必胜!
三、尼姆博奕(Nimm Game)
有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。这种情况最有意思,它与二进制有密切关系,我们用(a,b,c)表示某种局势,首先(0,0,0)显然是奇异局势,无论谁面对奇异局势,都必然失败。第二种奇异局势是(0,n,n),只要与对手拿走一样多的物品,最后都将导致(0,0,0)。仔细分析一下,(1,2,3)也是奇异局势,无论对手如何拿,接下来都可以变为(0,n,n)的情形。
计算机算法里面有一种叫做按位模2加,也叫做异或的运算
(相异为1,相同为0)
,我们用符号( + )表示这种运算。这种运算和一般加法不同的一点是1+1=0 。先看( 1 , 2 , 3 )的按位模 2 加的结果:1 =二进制01
2 =二进制10
3 =二进制11 (+)
———————
0 =二进制00 (注意不进位)
对于奇异局势(0,n,n)也一样,结果也是0。
任何奇异局势(a,b,c)都有a(+)b(+)c =0。
如果我们面对的是一个非奇异局势(a,b,c),要如何变为奇异局势呢?假设 a < b< c,我们只要将 c 变为 a(+)b,即可,因为有如下的运算结果:a(+)b(+)(a(+)b)=(a(+)a)(+)(b(+)b)=0(+)0=0。要将c 变为a(+)b,只要从 c中减去 c-(a(+)b)即可。
这道题就是用的尼姆博弈,只要找到怎样改变使可移动的台阶数之间异或相加之后结果为0就是必赢局面,如果没有移动之前异或相加的值就是0,那么是必输局面。在这道题目中需要先求出n个和尚中每个和尚可以向上移动的台阶数量,再将和尚两两分组,1和2、3和4、......、2i-1和2i,只需要求出两两分组之间台阶数的异或相加值就可以判断是否为必赢局面,因为移动3可以通过移动2达到同样的异或结果,同理移动4也可以通过移动5达到同样的异或结果,所以有影响的只有两两组合之间的台阶数。
#include<iostream>
using namespace std;
int main()
{
int a[101]; //用来存放和尚站的阶梯位置
int i=0,t=0;
char ch;
ch = getchar();
while(ch!='\n') //输入数组
{
if(ch>='0'&&ch<='9') //数字就存入数组
{
t = t*10 + ch-'0';
a[i] = t;
}
else if(ch == ' ') //遇到空格数组下标+1
{
t = 0;
i++;
}
ch = getchar();
}
int n = i;
int b[101]; //存放和尚与和尚之间阶梯数量
for(i=0;i<n;i++)
b[i] = a[i+1]-a[i]-1;
b[n] = 0;
int num = 0;
for(i=0;i<n;i=i+2)
num^=b[i]; //异或相加
if(num == 0) //异或相加之后结果为0 是必输局面
cout<<"-1"<<endl;
else //寻找怎样使局面成为必赢局面
{
int j,k;
for(i=0;i<n;i++) //从第一个和尚开始枚举
{
for(j=1;j<=b[i];j++) //从走一节台阶开始枚举
{
b[i] = b[i]-j;
if(i>0)
b[i-1] = b[i-1]+j;
num = 0; //重新计算走了j个阶梯后异或的值
for(k=0;k<n;k=k+2)
num^=b[k]; //异或相加
if(num == 0)
{
cout<<a[i]<<" "<<a[i]+j<<endl;
break;
}
b[i] = b[i]+j;
if(i>0)
b[i-1] = b[i-1]-j;
}
if(num == 0)
break;
}
}
return 0;
}