1022: [SHOI2008]小约翰的游戏John
Time Limit: 1 Sec Memory Limit: 162 MB
Submit: 1635 Solved: 1036
[Submit][Status][Discuss]
Description
小约翰经常和他的哥哥玩一个非常有趣的游戏:桌子上有n堆石子,小约翰和他的哥哥轮流取石子,每个人取的时候,可以随意选择一堆石子,在这堆石子中取走任意多的石子,但不能一粒石子也不取,我们规定取到最后一粒石子的人算输。小约翰相当固执,他坚持认为先取的人有很大的优势,所以他总是先取石子,而他的哥哥就聪明多了,他从来没有在游戏中犯过错误。小约翰一怒之前请你来做他的参谋。自然,你应该先写一个程序,预测一下谁将获得游戏的胜利。
Input
本题的输入由多组数据组成,第一行包括一个整数T,表示输入总共有T组数据(T≤500)。每组数据的第一行包括一个整数N(N≤50),表示共有N堆石子,接下来有N个不超过5000的整数,分别表示每堆石子的数目。
Output
每组数据的输出占一行,每行输出一个单词。如果约翰能赢得比赛,则输出“John”,否则输出“Brother”,请注意单词的大小写。
Sample Input
3
3 5 1
1
1
Sample Output
Brother
HINT
【数据规模】
对于40%的数据,T ≤ 250。
对于100%的数据,T ≤ 500。
Source
题解:系统的学了一下博弈论。。。妈妈呀。。。
具体去膜贾志豪的集训队论文好了。。。
对于这道题,zidaneandmessi:
①石子数全是1的时候,是最简单的情况。堆数为奇数后手必胜,为偶数先手必胜。
②只有两堆石子数不为1,且它们的石子数相等,后手必胜,理由如下:
(1)先手若取走一堆,后手可以取走另一堆或者剩下一个,回到状态①。
(2)先手若取一堆剩一个,后手可以取走另一堆或剩一个,回到状态①。
(3)先手若取一堆剩下多于一个,后手可以在另一堆中取走相同个数的石子,重复状态②(3),直到两堆都只剩两个石子,先手必须选择②(1)或②(2)。
③只有两堆石子数不为1,且它们的石子数不相等。先手必胜。先手可以从多的一堆里取,使两堆石子相等,回到状态②。
④剩下偶数堆,由多对相等且不为1的石子堆组成。后手必胜。无论先手如何取,后手都在配对的堆中取相同个数的石子,这样可以使相等的堆两两消去,最后转为状态②。
⑤剩下偶数堆。由同样的多对不相等且不为1的石子堆组成。后手必胜。后手可以用③的方法每次使配对的两堆石子数相等,转为状态④。
⑥剩下部分是同样的多对不相等且不为1的石子堆,其余堆石子数都相等。
(1)相等的堆数为偶数,后手必胜。后手可以按⑤的方法转到状态②获胜。
(2)相等的堆数为奇数,先手必胜。先手从相等的拿走一堆后就变成了⑥(1)。
行了,就此打住!如果再这么写下去,恐怕是无止境了。写到这里,我们发现后手总是尽可能地制造配对,并想方设法地回到他最擅长的状态②。但是知道了这个并没有多大用处,因为对于抽象的问题,还是无法下手。
但是我们发现了,如果能配对成功,a1^a2^...^an=0这个等式确实是成立的,因为以异或运算满足交换律和结合律,把配对的两个异或结果算出之后,就全变成0了。
如果a1^a2^...^an结果不为0呢?那么后手就倒霉了。因为先手总能找出一种拿石子的方式,把异或变成0。设结果为x,则每一个ai异或x后肯定有增有减,不可能全都增加。至少,为结果x提供最高位1的那个ai异或x后最高位变0了,一定是减少的。只要先手把它拿成剩下x个,新局面的异或结果就是0了。
那么后手有没有翻盘的可能呢?答案是否定的。因为一旦异或结果变成0后,再拿只会让异或结果不再是0。因为如果两次都是0了,设这次拿走石子把ai=m变成了ai=n(0<n<m),a1^a2^…^a(i-1)^a(i+1)^…^an的运算结果设为s,则s^n=0,s^m=0,说明n=m=s,与n<m矛盾,这样后手不论再怎么拿,都不能避免失利。
另外,如果结果为0,但是不能配对怎么办?很简单,根据上面的结论异或结果为0和不为0是交替变化的,只要先手每次移动使结果不为0,后手就再让结果为0就可以了,这样下去直到石子剩得不多了,胜负就很明显了。所以,后手一旦失误,先手就会使局势扭转!看似随意的游戏,实际上步步为营啊!
还没完,我们推导了半天把孤零零的状态①给忘了。注意到②有个条件是相等两堆石子数不为1,所以如果所有石子数都是1,直接套用①的结论即可。
代码们:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#define PAU putchar(' ')
#define ENT putchar('\n')
using namespace std;
inline int read(){
int x=,sig=;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')sig=-;ch=getchar();}
while(isdigit(ch))x=*x+ch-'',ch=getchar();
return x*=sig;
}
inline void write(int x){
if(x==){putchar('');return;}if(x<)putchar('-'),x=-x;
int len=,buf[];while(x)buf[len++]=x%,x/=;
for(int i=len-;i>=;i--)putchar(buf[i]+'');return;
}
void init(){
int x,t,n,T;bool f=true;
T=read();
while(T--){
n=read();f=true;t=;
for(int i=;i<=n;i++){
x=read();
if(x!=)f=;
t^=x;
}
if(f&&(n&))puts("Brother");
else if(f)puts("John");
else if(t)puts("John");
else puts("Brother");
}
return;
}
void work(){
return;
}
void print(){
return;
}
int main(){init();work();print();return ;}