【博弈论】Nim游戏/Nim博弈及其异或的详细易懂理解
Nim博弈简介
题面
有
n
n
n堆石子(
n
n
n
>
>
>
0
0
0),每一堆有
a
i
a_i
ai(
a
i
a_i
ai
>
>
>
0
0
0,
1
1
1
<
=
<=
<=
i
i
i
<
=
<=
<=
n
n
n)个石子。
每人每次可以从任意一堆石子里,取出任意多枚石子扔掉,可以取完,不能不取,每次只能从一堆里取。最后没有石子可以取的人输掉这场游戏。
设甲为先手,乙为后手,两个人以最佳策略进行操作。
给出
n
n
n,和这
n
n
n堆石子分别的数量,请问是否存在先手必胜的策略?
结论
如果这n堆石子的数量满足:
a
1
a_1
a1
x
o
r
xor
xor
a
2
a_2
a2
x
o
r
xor
xor
a
3
a_3
a3
x
o
r
xor
xor
.
.
.
...
...
x
o
r
xor
xor
a
n
a_n
an
=
=
=
0
0
0
则先手必败,否则先手必胜。
理解
要理解结论的式子,就要理解为什么使用异或,以及异或为0的意义。这两点将在接下来的理解中提到。
1.终结状态 和 异或
那么我们首先来看该问题的“终结点”(Terminal Position)是什么。
显然,当游戏结束的时候,场面上的情况为一粒石子都不剩,即:
a
1
a_1
a1
=
=
=
a
2
a_2
a2
=
=
=
a
3
a_3
a3
=
=
=
.
.
.
...
...
=
=
=
a
n
a_n
an
=
=
=
0
0
0
此时,显然有它们的异或和为0,即:
a
1
a_1
a1
x
o
r
xor
xor
a
2
a_2
a2
x
o
r
xor
xor
a
3
a_3
a3
x
o
r
xor
xor
.
.
.
...
...
x
o
r
xor
xor
a
n
a_n
an
=
=
=
0
0
0
而我们还知道一个特点,对一堆石子 a i a_i ai操作,将其变为一个数 n e w a i newa_i newai(当然,因为是拿出至少1个石子,所以 n e w a i newa_i newai小于 a i a_i ai),其实等价于对 a i a_i ai异或一个数字 x x x,来使得 a i a_i ai x o r xor xor x x x = = = n e w a a newa_a newaa.
因此我们可以得出第一个特性:玩家对某一堆石子的操作实际上等同于对该堆石子的异或。
用人话讲就是,你对
a
i
a_i
ai操作一次相当于把
a
i
a_i
ai变成了
n
e
w
a
i
newa_i
newai,只不过可以通过异或运算来达到目的。
就这么简单。
2.玩家的操作 与 异或
然后我们可以看一看玩家的每一次操作对所有石子数量的异或和产生了什么影响。
显然,如果当前状况是
a
1
a_1
a1
x
o
r
xor
xor
a
2
a_2
a2
x
o
r
xor
xor
a
3
a_3
a3
x
o
r
xor
xor
.
.
.
...
...
x
o
r
xor
xor
a
n
a_n
an
=
=
=
0
0
0
那么,上一个状况的时候,一定有异或和不为0,即:
a
1
a_1
a1
x
o
r
xor
xor
a
2
a_2
a2
x
o
r
xor
xor
a
3
a_3
a3
x
o
r
xor
xor
.
.
.
...
...
x
o
r
xor
xor
a
n
a_n
an
=
=
=
k
k
k(
k
k
k != 0)
原因是,这次操作一定会改变一个数
a
i
a_i
ai,且只改变
a
i
a_i
ai。
而现在,
a
i
a_i
ai与“其他数字的异或和”(注意断句)的异或结果为
k
k
k。如果把“其他数字的异或和”设为
x
x
x,那么现在的
a
i
a_i
ai与
x
x
x相等。
但是操作之前的
a
i
a_i
ai与
x
x
x不同,因此他们的异或结果
k
k
k不等于0。
由异或运算的结合律,上一个状况的时候,所有数字的异或结果不为0。
这个时候我们就发现了,既然第1部分中提到,对 a i a_i ai的操作就是对 a i a_i ai异或一次,那么根据异或运算的结合律,我们对 a i a_i ai的一次异或其实就是对总体结果(所有数字的异或和)的一次异或。
OK,那么我们就把整个游戏抽象为了一个异或运算的游戏:
保持原先的规则不变,我们的每次操作都可以让异或和改变,最后不能再操作的人将输掉游戏。
诶,这个时候就有同学要问了:异或和为0不能代表游戏结束啊,也不代表所有数都是0啊。
不着急,你现在一定已经有了一点感觉,但是还有一层窗户纸没有捅破。
下一节我会向各位同学解释如何使用异或和这个东西,并且如何利用异或运算在游戏规则内进行操作。
3.如何使用异或
那么我们假设一个一般的状态:
此时游戏进行到了某个阶段,场上的局面是这样的:
a
1
a_1
a1
x
o
r
xor
xor
a
2
a_2
a2
x
o
r
xor
xor
a
3
a_3
a3
x
o
r
xor
xor
.
.
.
...
...
x
o
r
xor
xor
a
n
a_n
an
=
=
=
k
k
k
这里的k可没有限制啊,可以等于0的。所以我们分情况讨论。
假设此时我是先手,你是后手。
①k不等于0的情况:
我们知道所有的数字异或的和为
k
k
k,
k
k
k不等于0。设
k
k
k的二进制有
i
i
i位,那么显然
k
k
k的最高位即
i
i
i位是1。
所以
a
1
a_1
a1到
a
n
a_n
an中一定有奇数个数字的第
i
i
i位为1(不然k的最高位怎么异或出来的是吧)。
不理解没关系,下面有个例子。
【比如,二进制情况下:
11001
x
o
r
10011
=
1010
11001 xor 10011 = 1010
11001xor10011=1010
1010当中的最高位1就是来自11001的第4位的1,一共有一个数的第4位为1】
理解了这一点,我们就可以想出一个骚操作:你不是异或得到 k k k吗,我的目标是让你面对的所有数字都变成0,自然,异或和也就是0了。那么我就要想办法减少你的某一个数字(拿走某一堆中的一些石子)。
所以,怎么减少呢:我们发现,如果让所有数字的异或和再异或它的结果
k
k
k,就可以让它变成0。
而从第2部分中提到的异或的结合律当中,我们可以发现,对异或和异或其实可以用结合律变成对其中的一个数异或。
那么我们要异或谁呢?啊,之前不是提到了那奇数个第
i
i
i位(
i
i
i是k的位数)为1的数吗,就选你们当中的一个了。
为啥呢,因为异或运算的一个性质,马上就讲。
设
a
j
a_j
aj第
i
i
i位为1,那么
a
j
a_j
aj
x
o
r
xor
xor
k
k
k
=
=
=
n
e
w
a
j
newa_j
newaj
n
e
w
a
j
newa_j
newaj一定是小于
a
j
a_j
aj的,因为
k
k
k的最高位即第
i
i
i位也是1。因此对
a
j
a_j
aj异或
k
k
k之后,
n
e
w
a
j
newa_j
newaj当中高于第
i
i
i位的那些数字(即第
i
i
i位左侧的那些数字)不会被异或
k
k
k影响到,而第
i
i
i位直接变成0,小于第
i
i
i位的无论如何变化都不会影响第
i
i
i位已经变成0的事实。
也就是
n
e
w
a
j
newa_j
newaj的第
i
i
i位是0,而
a
j
a_j
aj的第
i
i
i位是1,且更高位相同,因此
n
e
w
a
j
newa_j
newaj一定小于
a
j
a_j
aj。
【比如,11000 xor 1111 = 10111,而10111<11000】
很好,异或完毕之后我们就发现这样两件事:
(为了避免影响观看体验,我将xor替换为了^,C++的异或运算符)
a
1
a_1
a1 ^
a
2
a_2
a2 ^
a
3
a_3
a3 ^
.
.
.
...
... ^
a
n
a_n
an ^
k
k
k
=
=
=
k
k
k ^
k
k
k
=
=
=
0
0
0
而且
a
1
a_1
a1 ^
a
2
a_2
a2 ^
a
3
a_3
a3 ^
.
.
.
...
... ^
a
n
a_n
an ^
k
k
k
=
=
=
a
1
a_1
a1 ^
a
2
a_2
a2 ^
.
.
.
...
... ^
(
a
j
(a_j
(aj ^
k
)
k)
k) ^
.
.
.
...
... ^
a
n
a_n
an
=
=
=
a
1
a_1
a1 ^
a
2
a_2
a2 ^
.
.
.
...
... ^
n
e
w
a
j
newa_j
newaj ^
.
.
.
...
... ^
a
n
a_n
an
这样,我的异或操作就是符合游戏规则的(因为我确实拿走了一些石子,减少了某一堆石子的个数)
这个时候轮到你了,你的任何操作,无论异或任何的非零正整数,都会使得异或和即
a
1
a_1
a1 ^
a
2
a_2
a2 ^
a
3
a_3
a3 ^
.
.
.
...
... ^
a
n
a_n
an的结果从0变成非零的
n
e
w
k
newk
newk
而我只需要重复上面的操作,只不过把
k
k
k变为
n
e
w
k
newk
newk,然后重新寻找
a
j
a_j
aj而已。因为我们的操作一定是减少有限的石子总数的,而且我面对的永远是
a
1
a_1
a1 ^
a
2
a_2
a2 ^
a
3
a_3
a3 ^
.
.
.
...
... ^
a
n
a_n
an不为0的状况,你面对的永远是
a
1
a_1
a1 ^
a
2
a_2
a2 ^
a
3
a_3
a3 ^
.
.
.
...
... ^
a
n
a_n
an等于0的状况,所以一定在某次操作之后,你发现的异或和为0的状况是由于所有石子都被我拿完了而导致的,此时你将会输掉游戏。
发现了吗,k不为0的时候我是稳赢的,因为我的每一步都会使得你面对一个 a 1 a_1 a1 ^ a 2 a_2 a2 ^ a 3 a_3 a3 ^ . . . ... ... ^ a n a_n an等于0的状况,且我们一直在减少石子总量,所以石子总量为0的状况一定出现在你的回合。
而我是先手,所以k不为0时先手必胜。
②k等于0的情况:
风水轮流转,这个时候我发现坏了,我的任何操作都会导致异或和不为0,而你可以用①中的操作不断逼迫我,直到我无路可走。
而我是先手,所以k为0时先手必败。
注:因为题意中提到了:双方,也就是你和我,都是绝顶聪明的,使用最佳策略,所以不存在失误,任何一个先手必胜的情况下都会导致先手必胜的结局。
代码
#include<iostream>
using namespace std;
int t;
int main()
{
cin >> t;
while(t--)//t组测试数据
{
int n, x, k = 0;//初始化异或和k为0,因为任何数异或0都是它本身
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> x, k ^= x;//读入每一堆石子的同时就求出了异或和k
cout << (k ? "Yes" : "No") << endl;
//如果异或和k不为0则先手必胜,否则先手必败
}
return 0;
}
致谢
非常感谢各位阅读我的博客,也希望自己的一点拙见能够帮到各位。
转载请标注转自千寒的CSDN博客,谢谢!