P2197 【模板】nim 游戏(转自洛谷)
题目描述
甲,乙两个人玩 nim 取石子游戏。
nim 游戏的规则是这样的:地上有 n 堆石子(每堆石子数量小于 10^4),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这 n 堆石子的数量,他想知道是否存在先手必胜的策略。
输入格式
本题有多组测试数据。
第一行一个整数 T (T≤10),表示有 T 组数据
接下来每两行是一组数据,第一行一个整数 n,表示有 n 堆石子,n≤10^4。
第二行有 n 个数,表示每一堆石子的数量。
输出格式
共 T 行,每行表示如果对于这组数据存在先手必胜策略则输出 `Yes`,否则输出 `No`。
样例输入 #1
2
2
1 1
2
1 0
样例输出 #1
No
Yes
题目思路:
必胜策略,即当你执行最后一步后,你的对手已无石子可取,他必败你必胜。最后局面是地上各个石子堆已无石子可取,假设 ai 代表第 i 个石子堆中的石子数,则 ai = 0(i ≤ n)。即可得,a1^a2^a3^……^an=0(^表示异或),令ans = a1^a2^a3^……^an,要想必胜,说明最后的局面一定是ans==0,那我只需要满足,我每次操作完使得ans==0即可,那么每次留给对手的局面都是ans==0(怎么实现后面讲),并且石子的初始数确定,每次都取走一部分的石子,在有限的步数内肯定会取完,当执行到最后一步ans==0时,恰好最后的石子被你取完,你必胜。
那么,判断是否存在先手必胜策略这一问题,即可一开始检验石子堆中的ans是否为0,当ans为0时,你面对的是ans==0的局面,你的任意操作都会使得当前ans不为0,你的对手又足够聪明,那你每执行一步,他都会再次把ans==0的局面返还给你,那你最后必输;当ans不为0时,你先手,你又足够聪明,你每次执行后都可以把ans==0的局面留给对手,最后你必胜。
小tip:
为什么每次操作之后都会使得ans==0转化成ans != 0,并且足够聪明的你执行完一次操作之后又能把ans != 0变成ans==0?
1、ans==0执行一次操作之后为什么会转化为ans != 0?
异或,即是把每个ai中转化为二进制的形式,再对每一个位置的所有0和1进行异或操作,举个例子:5^3=6(0101^0011=0110)。而ans = a1^a2^a3^……^an,每次只能对一个石子堆操作,即每次操作只会影响ai(i ≤ n)中的一个,假设是对ak进行操作,则(没操作前的ak)^(其它ai的异或)==0,说明(其它ai的异或)==(没操作前的ak)(因为两个相同的数相异或结果才为0),现在对ak进行操作,则ak的值肯定会变化,其二进制形式也随之变化,则(操作后的ak)!=(其它ai的异或),所以(操作后的ak)^(其它ai的异或)!= 0,则ans != 0。
2、为什么足够聪明的你执行完一次操作之后又能把ans != 0变成ans==0?
ans = a1^a2^a3^……^an,是各个位置多个0和1的异或,因为ans != 0,说明一定有个别位置中1的个数为奇数,则我们可以把该位置上的1去掉一个或增加一个,即取走一些石头,使之为偶数,则ans==0。例如:10^5=15(1010^0101=1111),这是最经典的了,我们可以从10中拿走5,则5^5=0,所以想说明的是,总有办法使ans==0。
AC代码:
#include <iostream>
using namespace std;
int main()
{
int t=0;
scanf("%d", &t);
int n=0;
string str[11];
for(int i=1; i<=t; i++)
{
scanf("%d", &n);
int ans=0, temp=0;
for(int j=1; j<=n; j++)
{
scanf("%d", &temp);
ans=ans^temp;
}
if(ans==0)str[i]="No";
else str[i]="Yes";
}
for(int i=1; i<t; i++)
{
cout<<str[i]<<endl;
}
cout<<str[t];
return 0;
}
希望能帮助到大家!