Nim游戏
Nim游戏是博弈论中最简单的模型,它本身属于公平组合游戏(Impartial Combination Game)的一种,它一般满足3个条件:
1. 两位玩家交替从有限的合法移动集合中选择一种,对游戏进行移动;
2. 合法移动集合只取决于游戏局面本身,不受当前移动者、过去的某种局面以及其他随机因素如色子点数等(不符合该条件的一个典例就是象棋,其合法移动集合受到当前移动者和过去某种局面的影响,因为红方只能移动红棋,黑方只能移动黑棋,并且不允许相同局面出现多次);
3. 合法移动集合为空,即无法进行任何移动时,当前轮到的玩家失败。
对于游戏中的所有石子堆来说,当每一堆石子都空了的时候,合法移动集合就一定为空了,所以游戏的主要目标就是尽力引导另一位玩家,使其在某次移动时出现全空了的状态,因此需要定义两种状态:本家必胜状态(N-Position)和上家必胜状态(P-Position)。任何一种游戏状态都可以按照以下3条规则判断是P还是N:
1. 无法进行任何移动的为P;
2. N一定存在某种变为P的合法移动方式;
3. P的所有合法移动都将转化为N,不存在任何一种保持P的移动方式。
先从某些特殊情况开始,假定初始盘面为{2,3},那么先手玩家只要从第2堆石子中移走1枚变为{2,2},后手玩家无论取哪一堆,数量如何,先手只要能保证两堆石子数量相同,后手必败,那么局面{2,3}就是一个N-Position,而{2,2}就是一个P-Position。对于两堆石子而言,两堆的数量相同时为P,不同时为N。
换一种方式思考,当两堆石子的数量{n1,n2}不同时,假设n1>n2,那么n1就可以视作两个子堆{n1−n2,n2},有两个n2可以一一对应,而剩余的n1−n2没有与其对应的另一堆,该状态为N;而当先手玩家取走n1−n2个石子之后,留下的局面就只剩之前互相对应的两堆n2,该状态为P。由此可以得出一个结论,按照某种划分方式将每堆划分为若干子堆后,每一个子堆都可以与另一个相同大小的子堆对应的局面为P,存在某些无法找到对应子堆的局面为N
如此一来就可以向堆数更多的情况扩展。利用二进制的思想,每一堆都可以分解为若干数量为2的幂次的子堆,如2510=110012,那么25就可以视作{16,8,1}即{24,23,20}。对于任意局面{a1,a2,…,an},将每一堆的数量ai转化为二进制数bmib(m−1)i…b1ib0i,那么大小相等的子堆数量为偶数时,每一个子堆都可以与另一个相同大小的子堆一一对应,而大小相等的子堆数量为奇数时,就会存在某些子堆无法找到对应关系。
下面以局面{7,9,15,12}为例:
很显然,它们二进制的每一位上,1的数量之和不全是偶数,对于大小为8,4,1的子堆来说,它们的数量为奇数,只有大小为2的子堆数量才是偶数,此时的状态为N。先手玩家从12的一堆中拿走11,就变成了{7,9,15,1}的P状态。
此后无论后手玩家拿多少,先手玩家只要保证和后手玩家拿的数量相同,就可以将全空的P状态交给后手玩家。下面是游戏进行的完整步骤:
上面提到的子堆其实就是二进制位的1,每一位上1的数量的奇偶性决定了该位进行异或(xor)运算后的结果是1还是0,奇数是1,偶数是0。对初始局面中每一堆的数量两两异或后,如果结果的每一位都是0,就说明该局面是P状态,否则为N状态,由此得到了Nim游戏的重要结论即包顿定理(Bouton’s Theorem):对于局面{a0,a1,…,an−1,an},令Sn=a0 xor a1 xor … xor an,那么Sn=0的局面为P即后手必胜,而Sn≠0的局面为N即先手必胜。
C++ 代码
代码很简单,但是背后原理有点复杂,参考了博客园的一些帖子,以及提出包顿定理的论文Nim, A Game with a Complete Mathematical Theory之后,完成了本帖
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
size_t x, ans = 0;
while (n--) {
cin >> x;
ans ^= x;
}
cout << ((ans != 0) ? "Yes" : "No") << endl;
return 0;
}