一、Nim游戏介绍
给定\(n\)堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
据说,它源自中国,经由被贩卖到美洲的奴工们外传。辛苦的工人们,在工作闲暇之余,用石头玩游戏以排遣寂寞。后来流传到高级人士,则用便士(Pennies),在酒吧柜台上玩。
最有名的玩法,是把十二枚便士放成\(3、4、5\)三列,拿光铜板的人赢。
后来,大家发现,先取的人只要在\(3\)那列里取走\(2\)枚,变成了\(1、4、5\),就能稳操胜券了,游戏也就变得无趣了。
于是大家就增加列数,增加铜板的数量,这样就让人们有了毫无规律的感觉,不易于把握。
二、举个栗子
-
\(2\)和\(3\)两堆石子,甲先来,从\(3\)那堆中取\(1\)个,留下两堆,分别是\(2\)和\(2\)。
-
以后不管乙怎么拿,甲都和他在相反的一堆中做同样的操作,这样,肯定是乙最先面对\(0\),\(0\)状态。
三、先上结论
在两名选手都足够聪明,每一步都是最优解的情况下:
\(a_1\) ^ \(a_2\) ^ ... ^\(a_n=0\) 先手必败
\(a_1\) ^ \(a_2\) ^ ... ^\(a_n\neq0\) 先手必胜
就是把所有堆的石子个数异或起来,结果是零,先手必败,不是零,先手必胜。
怎么样,是不是很神奇?我看到它的时候也觉得很神奇,完全没有道理的和异或运算扯上了关系。但这个定理的证明却也不复杂:
四、证明
分情况讨论:
(1)每一堆中的石子的个数都是零,不能再进行任何操作,没有可以拿的东西,就是输了,\(a_1\)$a_2$...^\(a_n\)=0 ^ 0 ... 0=0,此时当前操作人输了,如果是上来就是这种情况,那么先手必败,此时上面的Bouton结论是正确的。
(2)如果\(a_1\)$a_2$...^\(a_n=x \neq0\) ,一定可以通过拿走某一堆中的一些石子,把式子的异或值变成0。
证明:我们先找到x的二进制表示中最高的一位1,设是第k位。说明\(a_1\)~\(a_n\)中必然至少有一个数\(a_i\),\(a_i\)的第k位的值是1.(要不这个位置的1是咋来的?)
那么 $a_i $^ x \(< a_i\) (因为\(a_i\)的第k位是1,x的第k位也是1,这样两个数字异或,只有一种可能,就是变小),我们就可以在\(a_i\)中拿走$ a_i-a_i$ $x$个石子(这个值肯定是正的)。这时,$a_i$这堆石子的个数就变成了$a_i-(a_i-a_i$\(x)\)= \(a_i\) ^x个。
那么现在的异或结果就是:
\(\ \ \ \ \ \ \ a_1\)^\(a_2\) ...^\(a_i\) ^ \(x\).. ^\(a_n\)
\(\because\) \(a_1\)$a_2$...^\(a_i\) ...... ^\(a_n =x\) 代入上边的式子
\(\therefore\) $ x ^{\wedge} x=0$ 证毕,我们一定可以通过拿走一堆中的一些石子,达到\(a_1\)$a_2$...^\(a_n=0\) 的目标。
(3)如果\(a_1\)$a_2$...^\(a_n=0\)的情况,那么不管怎么去拿,拿完之后的所有数的异或值肯定不是零。
反证法
(假设从任意一堆里去拿大于零的石子个数,拿完后,异或值还是零,然后我们证明这样做是不对滴~):
假设 我们在第i堆里去拿,拿完后第i堆的石子个数为\(a_i^{\prime}\)。
则:
\(a_1\)^ \(a_2\)^ ...^ \(a_i^{\prime}\) ^ \(a_{i+1}\).. ^ \(a_n=0\)
与
\(a_1\) ^ \(a_2\) ^ ...^ \(a_i\) ^ \(a_{i+1}\).. ^\(a_n=0\)
左右等式两边直接做异或运算,因为异或运算满足消去率,最终成为:
\(a_i^{\prime}\) $ ^{\wedge} a_i=0\(
也就是\)a_i=$ \(a_i^{\prime}\),也就是啥也没拿!啥也不拿是不行的,因为我们说好了是拿走的,就证明了是假设不对,拿完后异或值肯定不是零!
所以,最开始的状态,
(1)如果所有元素的异或值是零(第3种情况),甲不管怎么拿,剩余所有元素的异或值都将不是零,就是转为第2种情况。
(2)如果所有元素的异或值不是零(第2种情况),甲一定能有一种办法去拿,使得剩余所有元素的异或值是零,就是转为第3种情况。
上面的两个状态不断的重复转换,最终将达到第1种情况,就是没有东西可以拿了。至于谁可以赢,其实就是两个原因:
(1)最开始时各堆石子的异或值。
(2)在面临异或值不是零的情况下,不犯错误,一直按必胜的方法操作,则获胜。
那么,这个游戏在面临异或值不是零的情况下,怎么样操作可以达到所有元素的异或值是零的呢?
(1)计算出所有元素的异或值x.
(2)找出x的二进制中最高位是1的位置k。
(3)找到所有堆中,数量值的二进制第k位也是1的那个(可能不只一个,随便找一个就行),比如是\(a_i\)
(4)在\(a_i\)中,拿走 \(a_i-a_i\)^\(x\)个。
五、Bouton是如何想到这个方法的
为了进一步理解Nim取子游戏,我们考查某些特殊情况。如果游戏开始时只有一堆硬币,游戏人I则通过取走所有的硬币而获胜。现在设有2堆硬币,且硬币数量分别为N1和N2。
游戏人取得胜利并不在于N1和N2的值具体是多少,而是取决于它们是否相等。设N1!=N2 ,游戏人I从大堆中取走的硬币使得两堆硬币数量相等,于是,游戏人I以后每次取子的数量与游戏人II相等而最终获胜。但是如果N1= N2,则:游戏人II只要按着游戏人I取子的数量在另一堆中取相等数量的硬币,最终获胜者将会是游戏人II。这样,两堆的取子获胜策略就已经找到了。
现在我们如何从两堆的取子策略扩展到任意堆数中呢?
首先来回忆一下,每个正整数都有对应的一个二进制数,例如:\(57_{(10)}=111001_{(2)}\),即:\(57_{(10)}=2^5+2^4+2^3+2^0\)。于是,我们可以认为每一堆硬币数由2的幂数的子堆组成。这样,含有57枚硬币大堆就能看成是分别由数量为\(2^5、2^4、2^3、2^0\)的各个子堆组成。
现在考虑各大堆大小分别为\(N_1,N_2,N3,...,N_k\)的一般的Nim取子游戏。将每一个数\(N_i\)表示为其二进制数(数的位数相等,不等时在前面补0):
\(N_1=a_s..a_1a_0\)
\(N_2=b_s..b_1b_0\)
\(.....\)
\(N_k=m_s..m_1m_0\)
如果每一种大小的子堆的个数都是偶数,我们就称Nim取子游戏是平衡的,而对应位相加是偶数的称为平衡位,否则称为非平衡位。因此,Nim取子游戏是平衡的,当且仅当:
\(a_s+b_s+...+m_s\) 是偶数
.....
\(a_1+b_1+...+m_1\) 是偶数
\(a_0+b_0+...+m_0\) 是偶数
于是,我们就能得出获胜策略:
游戏人I能够在非平衡取子游戏中取胜,而游戏人II能够在平衡的取子游戏中取胜。
我们以一个两堆硬币的Nim取子游戏作为试验。设游戏开始时游戏处于非平衡状态。这样,游戏人I就能通过一种取子方式使得他取子后留给游戏人II的是一个平衡状态下的游戏,接着无论游戏人II如何取子,再留给游戏人I的一定是一个非平衡状态游戏,如此反复进行,当游戏人II在最后一次平衡状态下取子后,游戏人I便能一次性取走所有的硬币而获胜。而如果游戏开始时游戏牌平衡状态,那根据上述方式取子,最终游戏人II能获胜。
下面应用此获胜策略来考虑4-堆的Nim取子游戏。其中各堆的大小分别为7,9,12,15枚硬币。用二进制表示各数分别为:0111,1001,1100和1111。于是可得到如下一表:
\(2^3=8\) | \(2^2=4\) | \(2^1=2\) | \(2^0=1\) | |
---|---|---|---|---|
大小为7的堆 | 0 | 1 | 1 | 1 |
大小为9的堆 | 1 | 0 | 0 | 1 |
大小为12的堆 | 1 | 1 | 0 | 0 |
大小为15的堆 | 1 | 1 | 1 | 1 |
由Nim取子游戏的平衡条件可知,此游戏是一个非平衡状态的取子游戏,因此,游戏人I在按获胜策略进行取子游戏下将一定能够取得最终的胜利。具体做法有多种,游戏人I可以从大小为12的堆中取走11枚硬币,使得游戏达到平衡(如下表),
\(2^3=8\) | \(2^2=4\) | \(2^1=2\) | \(2^0=1\) | |
---|---|---|---|---|
大小为7的堆 | 0 | 1 | 1 | 1 |
大小为9的堆 | 1 | 0 | 0 | 1 |
大小为12的堆 | 0 | 0 | 0 | 1 |
大小为15的堆 | 1 | 1 | 1 | 1 |
之后,无论游戏人II如何取子,游戏人I在取子后仍使得游戏达到平衡。
同样的道理,游戏人I也可以选择大小为9的堆并取走5枚硬币而剩下4枚,或者,游戏人I从大小为15的堆中取走13枚而留下2枚。
归根结底,Nim取子游戏的关键在于游戏开始时游戏处于何种状态(平衡或非平衡)和第一个游戏人是否能够按照取子游戏的获胜策略来进行游戏。
既然和异或、二进制扯上了关系,那么是如何计算需要从哪个拿,拿几个就是巴顿老师当时的唯一需要解决的问题了:
(1)计算每一堆硬币数量的异或和,就是x=7912^15=\(1101_{(2)}\)=13
(2)找到x的最前面一个是1的位,本例就是1101中最前面的那个1.
(3)在给定的硬币堆中,找到一个和上面步骤2位数也是1的数字,比如本例我们找到的是12.
(4)计算 \(a_i-a_i\)^ \(x=12-12\)$13$=$1100_{(2)}-1100_{(2)}$\(1101_{(2)}=11\) 也就是在12这堆中,拿走11个硬币就达到了重新的平衡状态。
Tips:
(1)如果所有数成对出现,那么异或的最终结果是0.
(2)如果异或的最终结果是0,那它们不一定是成对出现的,比如 1,2,3,它们应成二进制就是01,10,11,异或在一起就是0,但不成对。
六、扩展问题
如果Nim游戏中的规则稍微变动一下,每次最多只能取K个,怎么处理?
方法是将每堆石子数mod (k+1).
七、C++ 代码
#include <iostream>
using namespace std;
const int N = 100010;
int main() {
int n;
scanf("%d", &n);
int res = 0;
while (n--) {
int x;
scanf("%d", &x);
res ^= x;
}
if (res) puts("Yes");
else puts("No");
return 0;
}