尼姆博奕(Nimm Game)
两个人从N堆石子中的任意一堆任取1个或多个,取到没得取得人为负者。
奇异局势
如果面对一种局势的人是必败的,那么这种局势叫做奇异局势。比如如果N堆石子都是0个,那么先取者面对的(0,0,0,…,0)就是一种奇异局势。
如果我想让我的对手输,我会想尽办法让他面对奇异局势。
奇异局势有这样一种性质,比如现在的N堆石子的数目为a1,a2,a3,…,aN,那么:
a1⊕a2⊕a3⊕…⊕aN=0。
a1⊕a2⊕a3⊕…⊕aN一般称为Nim-sum。
为什么Nim-sum=0的局势是奇异局势?
也就是说,为什么当玩家面对Nim-sum=0的时候,他是必输的?
这篇论文有比较好的解释:
http://web.mit.edu/sp.268/www/nim.pdf
下面试着翻译为中文:
证明:如果一个玩家想在尼姆博奕的游戏中胜出,他应该让Nim-sum值为0。
引理1 如果在某一轮中Nim-sum=0,那么下一次的操作必然会改变Nim-sum。
证:
令x1,x2,…,xn表示操作前N堆石子的个数,s表示操作前N堆石子的Nim-sum,那么:
s=x1⊕x2⊕x3⊕…⊕xn;
令y1,y2,…,yn表示操作后N堆石子的个数,t表示操作后N堆石子的Nim-sum,那么:
t=y1⊕y2⊕y3⊕…⊕yn;
令操作中改变的石子堆标号为k,那么在操作后必然有xk!=yk,且对于i!=k来说必然有xi=yi,说白了就是改变一个且只有一个石子堆。
那么:
t = 0 ⊕ t
= s ⊕ s ⊕ t
= s ⊕ (x1 ⊕ x2 ⊕ … ⊕ xn) ⊕ (y1 ⊕ y2 ⊕ … ⊕ yn)
= s ⊕ (x1 ⊕ y1) ⊕ (x2 ⊕ y2) ⊕ … ⊕ (xk ⊕ yk)
= s ⊕ xk ⊕ yk
由于xk!=yk,所以xk ⊕ yk != 0,而我们知道在操作前的Nim-sum=0,也就是s为0,所以t != 0,证毕。
引理二如果在某一轮中Nim-sum != 0,那么下一次的操作可以改变Nim-sum使其为0。
证:
以下字母除了说明都与引理一定义相同。
令d是s的最高有效位,我们选取一个石子堆xk,xk的最高有效位也是d(这是一定可以找得到的,因为s=x1⊕x2⊕x3⊕…⊕xn)。
那么令yk = s ⊕ xk,由于最高有效位相同,s ⊕ xk的值必然小于等于xk,比如 s = 100010,
xk= 101010,
此时yk= 001000≤xk。
所以我们总能够从xk中拿走xk ? yk个石子使得yk = s ⊕ xk。
那么t = s ⊕ xk ⊕ yk(由引理一)
= s ⊕ xk ⊕ xk ⊕ s
= s ⊕ s ⊕ xk ⊕ xk
= 0
证毕。
现在我们来回答开头的问题:为什么当玩家面对Nim-sum=0的时候,他是必输的?
当玩家A面对Nim-sum=0的时候,由引理一,他必然会使得Nim-sum!=0;由引理二,下一轮玩家B面对Nim-sum!=0时,他完全可以使Nim-sum=0。
如此循环往复,最后石子堆的情况将会是Nim-sum=0的极限情况,也就是a1=0,a2=0,…,an=0。
那么玩家A输定了。
思路
所以,这题中的问题是先手玩家有几种方法可以在第一轮操作后的石子堆为Nim-sum=0的状态,其实也就是引理二中的内容了。
先将游戏刚开始的Nim-sum算出来;
如果Nim-sum=0,那么先手玩家面对的就是奇异局势,他根本没有办法赢,方法数为0;
否则,由引理二,统计(Nim-sum ^ xi) <= xi的个数就是赢的方法数。
代码
#include <stdio.h>
int main() {
int CaseNum;
scanf("%d\n", &CaseNum);
while (CaseNum--) {
int N, Sum = 0, Stones[50005], k = 0, XorSum = 0;
scanf("%d\n", &N);
char C[600000];
gets(C);
// Use the gets() function to read a line with lots of datas is fast than scanf()
for (int i = 0; C[i] != ‘\0‘; i++) {
if (C[i] == ‘ ‘) {
Stones[k++] = Sum;
Sum = 0;
} else {
Sum = Sum * 10 + C[i] - ‘0‘;
}
}
Stones[k] = Sum;
for (int i = 0; i < N; i++) XorSum ^= Stones[i];
if (XorSum == 0) printf("0\n");
else {
int Ways = 0;
for (int i = 0; i < N; i++) if ((XorSum ^ Stones[i]) <= Stones[i]) Ways++;
printf("%d\n", Ways);
}
}
return 0;
}