题目描述:
给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数n。第二行包含n个数字,其中第 i 个数字表示第 i堆石子的数量。
输出格式
如果先手方必胜,则输出“Yes”。否则,输出“No”。
数据范围
1≤n≤105,1≤每堆石子数≤109
输入样例:
2
2 3
输出样例:
Yes
思路分析:
先给出Nim游戏的一些概念和结论:
-
给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。
-
我们把这种游戏称为
NIM博弈
。把游戏过程中面临的状态称为局面
。整局游戏第一个行动的称为先手
,第二个行动的称为后手
。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。 -
所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。
NIM博弈不存在平局,只有先手必胜和先手必败两种情况。
定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0
也就是说,当各堆石子的个数a1 ^ a2 ^ … ^ an = 0时,先手必败,否则先手必胜。我们先直观的去理解这个定理,对于第i堆石子,其石子个数为x,在x的二进制表示中,至多能为每位贡献一个1.。比如32位的整数,每一位至多有一个1,这是显然的。a1 ^ a2 ^ … ^ an = 0说明了在二进制的各位,1出现的次数都是偶数的。先手不论怎么拿,对二进制的各位而言,要么不影响某位1的总数,要么让某位1的总数多1或者少1。而后手只需要从某堆中拿的石子个数使得石子堆中各位1的总和恢复成偶数即可,由于各位1的总数开始是偶数,先手不可能将该位的1拿完(除非先手面临的就是必败态:石子已经被拿完了),所以后手永远有石子可拿,因此先手必败。不妨举个例子,为了表示方便,石子的个数都用二进制表示。设石子一共有3堆,个数分别为1111,1001,0110,可见初始状态的异或和是0。假设先手先从第一堆里拿走了1100,第一堆还剩0011,此时二进制中各位1的个数从左往右分别是1,1,2,2.。为了让前两位的1的个数恢复偶数,后手先想到从某堆里拿走1100,即与先手进行同样的操作,但是发现此时各堆石子的数目0011,1001,0110,没有哪个石子堆可以满足后手的需要,于是后手就想将二进制中1的个数变为0,2,2,2。即从第二堆石子中拿出100个石子,则第二堆还剩0111个石子。此时二进制各位1的总数又恢复偶数了,重复这类操作,直至先手面临的状态是0,0,0,0,即必败态。上面不是对这个定理进行严格的证明,只是想从直观的角度去解释这个定理。
下面正式证明该定理:
首先必败态,没有石子了,异或和是0。然后对于任意一种异或和非0的情况,我们都可以用一步操作使之变为异或和为0的情况。设a1 ^ a2 ^ … ^ an = x != 0,设x二进制最高位的1是在第k位,则n堆石子中至少有一堆石子的个数该位为1,设第i堆石子个数ai第k位为1,则ai^x < ai,(将ai的第k为从1变成0了,低位无论怎么变也不会超过原来ai的大小)。此时我们从第i堆中拿走ai - ai^x个石子,则第i堆石子个数变为ai - (ai - ai^x)= ai ^ x个,故a1 ^ … ^ ai ^ x ^ … ^an = a1 ^ a2 ^ … ^ an ^ x = x ^ x = 0。对于任意一种异或和为0的情况,我们拿走任意数量的石子都无法使得石子的异或和还是0.假设从第i堆拿走一定数量的石子使得ai变为ai’,a1 ^ … ^ ai’ ^ … ^ an = 0,又a1 ^ … ^ ai ^ … ^ an = 0,将两个式子左右两边分别异或起来得ai’ ^ ai = 0,故ai’ = ai,也就是什么都不拿,这是违背规则的,故假设不成立。对于异或和为0的状态,我们无法使之一步转化为异或和还是0的状态。现在我们找到了最终的必败态是石子数量为0,异或和为0.只要先手面对的是异或和非0的状态,则可以通过一步操作使之转化为异或和为0的状态,后手面对异或和为0的状态无论怎么操作都只能转化为异或和非0的状态,因此只要先手每次都采用最优策略,面临必败态的一定是后手。
AC代码:
#include<iostream>
#include<cstdio>
using namespace std;
int n;
int main()
{
int res = 0;
scanf("%d", &n);
while (n--) {
int x;
scanf("%d", &x);
res ^= x;
}
if (res) printf("Yes");
else printf("No");
system("pause");
return 0;
}