Nim游戏:
1. 一个状态是必败状态当且仅当它的所有后继都是必胜状态。
2. 一个状态是必胜状态当且仅当它至少有一个后继是必败状态。
对于Nim游戏来说,早有科学家给出了一个定理(Bouton定理):状态(x1,x2.....xn)为必败状态当且仅当x1^x2^......^xn= 0,即把所有数进行异或和操作,也称Nim sum。【能够证明,当Nim sum为0 时为必败状态,非0 时为必胜状态,这是因为,当前状态为0 时,进行的某个操作总能使Nim sum变为非0(因为改变任何一个数字(即任何一堆石子),它的二进制形式会有一位或多位的变化,此时原本各位上都为0 的Nim sum就会有所变动(某些位会变成1)),即所有后继都是必胜状态了;当前状态为非0 时,必定会有某个操作能使Nim sum变为0(只需在Nim sum二进制上那些为1的位上面着手即可),即一定有一个后继状态为必败态。这就是Bouton定理。】
Bouton定理实质上是SG定理的具体应用。什么是SG定理呢?
SG定理:
Sprague-Grundy定理(SG定理):游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。而Bouton定理就是Sprague-Grundy定理在Nim游戏中的直接应用,因为单堆的Nim游戏 SG函数满足 SG(x) = x。
对于SG定理:
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
这一步应该是非常简单的,就是定义了新的运算为mex。
对于任意状态 x , 定义 SG(x) = mex(S),其中S是 x 后继状态的SG函数值的集合(就是上述mex中的数值)。最后返回值(也就是SG(X))为0为必败点,不为零必胜点。
进一步解释一下S,就是题意中给出的可以移动的次数。举个例子来说,一堆石子,每次只能拿1,3,5,7个,那么S数组就是1,3,5,7。
假如说是在一个游戏中有多个石子堆该怎么办了。我们只需要把对每个石子堆进行sg函数的调用,将得到的所有的值进行异或。得出来的结果为0则该情形为必败态。否则为必胜态。
#include<cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int MAX_N = 100;
int sg[MAX_N];//sg函数
bool vis[MAX_N];//标记数组
void solve(int n) {
memset(vis, false, sizeof(vis));
for (int i = 0; i < n; ++i)
vis[sg[i]] = true;
for (int i = 1; i <= n; ++i)//因为可以分成两堆,如果三堆,就写三重循环
for (int j = 1; j <= n; ++j) {
if (i + j == n) vis[sg[i] ^ sg[j]] = true;
}
int i;
for (i = 0; ; ++i)//没有i < n,如果都不成立,最后i = n
if (!vis[i]) break;
sg[n] = i;
cout << "sg[" << n << "]=" << i << endl;
}