尼姆(Nim)游戏

不用博弈论的概念,通俗地讲一下 尼姆( Nim)游戏

现有若干堆石子,每堆石子的数量都是有限的,双人进行游戏,每个人在每次行动的时候可以“选择一堆石子并拿走若干颗,不能不拿”,没有石子可拿的人为输。是否有先手必胜的策略?

直接考虑这个问题有点难度,我们还是从最简单的只有两堆石子来讲起吧!
(a)当只有两堆石子,若两堆石子的数目一样多,那么先手必输。因为不管先手选择那一堆拿多少个,后手都可以选择另一堆拿同样多个,最终肯定是后手拿完两堆都空了,先手输。
(b)若两堆石子的数目不是一样多,那么先手必赢。先手只要选择较多石子的那一堆,然后拿走一部分使得两堆石子的数目一样多,就回到上面的(a)状态了,后手必输。

现在有n堆石子,什么情况下先手必赢呢? 【非常抱歉,之前考虑不周,下面的解法是错误的】
我们来把这个大问题划分成一些小问题,先将n堆石子两两分组,共k = (n+1)/2组(当n为奇数的时候,可以增加一堆石子数目为0的)。
现在两个人要同时玩这k个双堆石子的游戏,现在参与者的选择是先选一组,然后选择其中一堆,然后选择拿走多少个石子。
还是先从最简单的情况说起,假设这k组石子中每组里的两堆的数目都是相等的,那先手必输,因为不管先手选择哪一组,都是上面的(a)状态。即先手选择第i组的一堆中的xi个,则后手就选择第i组的另一堆的xi个(对称的状态)。

假设现在这k组中只有1组石子的数目不相等,其他的k-1组都相等,会出现什么情况?
先手必赢,因为他直接把那堆数目不等的石子变为相等就回到上面的k堆石子中每堆数目都相等的情况了。

假设 现在这k组中只有2组石子的数目不相等,其他的k-1组都相等,会出现什么情况?
先手必输,如果他选择石子数目相等的那组,后手作出对称的选择;如果他选择石子数目不等的那组,有两种情况:
(1)先手拿完后那组仍旧不等,那后手可以从这两组石子数目不等的里面的4堆任意选择一堆,拿走若干个,只要保证这两组依然是不等的即可;
(2)先手那完后那组石子数目相等,那后手把另外那组石子数目不等的变的相等即可。

....

上面的分析方法是错误的,因为对于1,1, 2, 3这样子的四组,不同的分组是结果不一样的。

我们再换一种角度重新分析一下这个问题:

我们在分析的过程中需要把n堆石子的个数都用二进制表示,例如上面 1,1, 2, 3的例子对应的就是:
0    1
0    1
1    0
1    1

1. 先从最简单的两堆石子(a1, a2)看起,当a1 = a2时,两排二进制数字一模一样,按位异或之后得到0,先手必输。
当a1 > a2时(a1 < a2的情况类似),不妨假设为(6,5),表示成二进制为:
1    1    0
1    0    1
按位异或后得到3 > 0,这个时候先手只要从a1中拿走部分(a1-a2),使得a2 = a1就可以必胜了。

两堆的时候先手拿走几个使得他们的异或结果为0很显然,那么三堆该怎么处理呢?

2. 当有三堆石子(a1, a2, a3)时,我们还是从例子说起
(1, 2, 3)表示成:
0    1
1    0
1    1
按位异或结果为0,结论是先手必输,我们现在来枚举一下解法,看看先手是否有这样的绝对劣势。
(1)如果先手选择a1,只能拿走1个,剩下2和3,后手将两堆都变成2,先手必输;
(2)如果先手选择a2,那么他肯定不能全部拿走,因为那样的话局势跟1一样,所以他可以选择拿走1个,然后剩余1,1,3,后手选择a3拿走3个,剩下1,1,先手必输;
(3)如果先手选择a3,那么首先他不能全拿走,理由同上。如果他拿走1个,变成1,2,2,后手选择a1全拿走,剩下2,2,先手必输;
如果先手拿走2个,变成1,2,1,后手选择a2全拿走,剩下1,1,先手必输;

那如果是(1, 2, 4)呢?他们的按位异或运算结果不为0,先手应该怎么做才能使得拿走几个后这些数字异或值为0,这样后手就具有绝对劣势了。
0    0    1
0    1    0
1    0    0
异或结果k = 111B = 7,k中为1的位表示3堆石子数目异或运算时那位肯定有个1是单独的,为了使得整体异或为0,我们需要给k表示的所有位为1的那位再配一个1,举个例子来说的话就是,如果异或结果k是
8 7 6 5 4 3 2 1
1 0 1 0 0 1 0 1
其中第1,3,6,8位为1表示,这些位还缺一个1才能异或的时候得到全0。很显然给k^k结果是0,但是这样是增加了k个石子啊,现在要减少石子,使得总的异或结果为0。

还是上面 (1, 2, 4)的例子,如果我们选择a3堆,把(1    0    0)变成(0    1    1)(即从a3堆中拿走1个石子)是不是异或结果就为0了,这和之前的异或结果k(1    1    1)有什么关联呢?
(1    0    0)把第一位取反,然后其余位与k异或,也就是 (1    0    0)^ (1    1    1)= (0    1    1)。
那么是不是任选一堆哪来跟k异或一下就得到我们想要的结果呢? (0    0    1)^ (1    1    1)= (1    1    0)。与另外的两堆异或,结果果然是0,但是不幸的是这不是一个有意义的解,因为这堆的1个石子变成了6个,哪里偷来5个石子?选a2堆结果也是一样没有意义。
为什么只有选择a3堆进行与k的异或是有意义的呢?这里的a3是根据什么原则选择的呢?
原则只有一个,选择k的最高位对应为1的那个数字,这里k的最高位为第三位,该位为1的只有第三堆了。之所以这个选择有效,是因为经过异或运算后,这位的1变成0,之后可能变成1的位都比该位低,所以最后的数字肯定比与k异或之前要小。

综上,对于 (1, 2, 4),先手选择a3堆,拿走1个石子,变成(1,2,3),后手必输。

3. 现在来推广上面的情况,假设有n堆石子,我们怎么做呢?

先说结论,如果n堆石子数目的二进制按位异或结果为0,那么先手必输,因为不管先手怎么选择,异或结果都会不为0,然后后手将其恢复为0即可。终态是所有堆石子均为0,异或结果为0,这时先手没有任何选择,输掉游戏。
如果n堆石子数目的按位异或结果不为0,那么先手只要根据下面的策略拿走部分石子,使得异或结果为0,那么根据上面那条结论,后手必输。

当n堆石子数目异或结果为k时,先手只需要从n堆中选择k的最高位对应的那位为1的那堆石子,不妨设为ai,然后将ai变成ai^k,也就是拿走(ai - (ai^k))个石子就可以了。
这里ai是肯定至少存在一个的,因为不然的话异或结果k的这位不可能成为1。当然可能会存在多个满足这样条件的ai,我们只需要随便选择一个即可。


你可能感兴趣的:(算法与面试)