首先我们看例题:P2197 nim游戏
题目描述
甲,乙两个人玩Nim取石子游戏。
nim游戏的规则是这样的:地上有n堆石子(每堆石子数量小于10000),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这n堆石子的数量,他想知道是否存在先手必胜的策略。
输入输出格式
输入格式:第一行一个整数T<=10,表示有T组数据
接下来每两行是一组数据,第一行一个整数n,表示有n堆石子,n<=10000;
第二行有n个数,表示每一堆石子的数量
输出格式:共T行,如果对于这组数据存在先手必胜策略则输出"Yes",否则输出"No",不包含引号,每个单词一行。
输入输出样例
2
2
1 1
2
1 0
No
Yes
讲解:
本题就是最最经典的nim游戏了。nim游戏过程中面临的状态叫做局面,第一个行动的为先手,第二个行动的为后手。考虑两人无比聪明,则必败局面仅当该局面所能到达的局面均为必败局面时出现,而必胜局面仅当后续局面存在至少1个必胜局面时出现,显然nim游戏中1为必胜局面(因为拿走1就赢了)。显然,nim游戏是不存在平局的,只有先手必赢或先手必输两种情况。
定理:设各堆为a1、a2…an,则nim游戏先手必赢仅当a1 Xor a2 Xor…Xor an≠0.
证明:
首先,当石子均被取完时,则a数组都为0,存在a1 Xor a2 Xor…Xor an=0,因为每次取都会使石子数减少,当前局面若a1 Xor a2 Xor…Xor an≠0,我们只要保证能在取走一些石子后使得a1 Xor a2 Xor…Xor an=0,则必然保证自己能取走最后一个石子获得胜利。
等价于证明:
(1)当a1 Xor a2 Xor…Xor an≠0时,存在某种取法使得剩下的石子xor和为0。
(2)当a1 Xor a2 Xor…Xor an=0时,不存在取法使得剩下的石子xor和为0。(即取走一些石子后必定Xor和不为0)
首先证明(1),对于任何一个局面a1 Xor a2 Xor…Xor an=x≠0,设x的二进制最高位的1在第k位,则至少存在一堆石子ai的二进制第k位是1(因为我们是Xor运算,某一位上的1不会凭空出现)且ai≥x。由Xor运算法则知:x Xor ai<ai,(因为至少会使第k为上的1变为0)。于是我们从ai这堆里取走一些石子,使得ai堆剩下的石子数变为ai Xor x,此时再对剩下的各堆进行上述运算:a1 Xor a2 Xor…ai Xor x…Xor an=x Xor x=0,此时Xor和为0。 于是得证(1)。
再来证明(2),对于任何一个局面a1 Xor a2 Xor…Xor ai Xor…an=0,我们反证:假设取走ai堆中的一些石子使ai变为了x,使得a1 Xor a2 Xor…Xor x Xor…an≠0,则显然是不可能的,因为开始Xor和就为0再由Xor运算的性质当ai变为x后若Xor和为0,当且仅当ai=x时成立。而nim游戏中不能不取,所以若当前局面Xor和为0,则必然会使下一局面Xor和不为0。于是(2)得证。
结论:nim游戏只要满足先手的Xor和不为0,则先手必赢,否则先手必输。
代码:
#include<bits/stdc++.h> #define il inline #define ll long long using namespace std; il ll gi() { ll a=0;char x=getchar();bool f=0; while((x<'0'||x>'9')&&x!='-')x=getchar(); if(x=='-')x=getchar(),f=1; while(x>='0'&&x<='9')a=a*10+x-48,x=getchar(); return f?-a:a; } ll t,n,a[100005]; int main() { t=gi(); while(t--){ n=gi();ll x=0; for(int i=1;i<=n;i++)a[i]=gi(),x^=a[i]; if(x)puts("Yes"); else puts("No"); } return 0; }