Nim游戏在ACM中碰到了,就拎出来写写。
一般Nim游戏:有n堆石子,每堆石子有$a_i$个,每次可以取每堆石子中$[0,a_i-1]$,问先手是否有必胜策略。
泛Nim游戏:每堆石子有$a_i$个,每次可以取每堆石子中若干个且有一定限制,问先手是否有必胜策略。
我们定义 :
P 表示 先手必败局
N 表示 后手必败局
性质1:对于Nim游戏任何局面要么是P要么是N。
给出 N P 状态的严谨定义(性质) :
1. 无法更改局面的局面是 P
2. 可移动到 P 的是 N
3. 所有移动都导致 N 的是 P
根据上述定义,我们可以知道,如何判定一个局面是P,
就是枚举它的所有移动,若全部后继状态都会移动到 N 那么就是 P
性质2:对于一般的Nim游戏,若存在$ \{a1,a2...an \} $的局面,那么
若$ a_1 \oplus a_2 \oplus ... \oplus a_n = 0 $那么是 P ; 否则是 N 。
对此我们只需要证明以下三个引理,就可以推出全部情况:
引理1:所有无法移动的局面都是P
引理2:若一个局面是$ \{a1,a2...an\} $,且 $ a_1 \oplus a_2 \oplus ... \oplus a_n \neq 0 $
那么必然可以把某个 $ a_i $ 改成 $ a_i'$ 让 $ a_1 \oplus a_2 \oplus ...a_i'... \oplus a_n = 0 $
证明 : 设$ a_1 \oplus a_2 \oplus ... \oplus a_n \neq 0 = k > 0 $
那么显然有奇数个$a_i$满足二进制表示最高位是 1 ,取其中一个最高位是 1 的 $a_i$ 改成 $ a_i=a_i \oplus k $
那么此时 , $ a1 \oplus a2… \oplus ai' \oplus ...an=a1 \oplus a2… \oplus ai \oplus k \oplus ...an=k \oplus k=0 $
显然$ a_i \oplus k (k \neq 0) < a_i $(至少消去最高位的1)成立,证毕。
引理3:若一个局面是$\{a1,a2...an\} $,且$ a1 \oplus a2...an=0 $,那么不存在某种合法引动改变任何一个数字$ ai=ai' (ai'<ai) $使得$ a1 \oplus a2… \oplus ai' \oplus ...an=0$
证明:已知$ a1 \oplus a2... \oplus ai...an=0 $且$ a1 \oplus a2… \oplus ai' \oplus ...an=0 $,把式子左右两边异或起来,由异或的消去律可知。
$ ai \oplus ai'=0 $ , 所以 必然的$ ai=ai' $,和 $ ai'<ai $ 矛盾。
证毕。
由于三个引理成立,那么性质2成立。
关于一般的 Impartial Combinatorial Games 的模型 :
Sprague-Garundy 函数
一般模型:一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。
任何一个ICG都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。
我们定义mex{}表示集合中最小的没有出现过的非负整数。
mex{0,1,2,4}=3 , mex{0}=0等。
$sg(x) = mex \{ sg(y) | y是x的后继 \} $,可以理解为$ sg(x) $表示$x$所在子树节点y中$sg(y)$最小没有出现过的非负整数。
考察SG函数的性质:
1. 图上没有出边的点$ sg=0 $,因为后续集合都是空集。
2. 若一个$ sg(x)=0$的顶点,所有后继$y$满足$ sg(y)\neq 0$
3. 若一个$ sg(x)\neq 0$的顶点,必然存在一个后继$y$满足$sg(y)=0$
我们假设每个顶点都是一个决策,那么对于$x$节点的决策,若是P那么$sg(x)=0$,若是N那么$sg(x)\neq 0$
若扩展这个模型,把原来1颗棋子换成n颗棋子,由$ sg(x) $的定义可知
性质3: 当$ sg(x)=k $时,对于任意$i$满足$ i \in [0,k)$都存在一个$ y $ 是$ x $的后继满足$ sg(y)=i $ (根据sg的性质显然),不存在一个$y$满足$sg(y)=k$
考虑$sg(x)$和Nim原版游戏的相似处:Nim原版游戏是对于一堆棋子有$k$个,可以变成$[0,k-1]$,不能变成$k$个,不存在一个后继状态又取这堆棋子又这堆棋子一个不取。
至此, 我们已经把$sg(x)$和Nim游戏建立起高度等价的关系,$sg(x)$满足所有Nim游戏a数组的性质。
如果我们的有向图上问题是由多个Nim游戏拼接起来的,那么我们依然可以使用Nim游戏的思想来解决问题。
设$ G_1,G_2,G_3...G_n$是若干个有向图游戏,显然,对于每个$G_i$都满足Nim游戏的性质,若$n$个棋子在同一个有向图中移动的情况已经考虑过了
若$ n $个棋子不在同一个有向图中,我们可以看做,每次选择一个棋子所在的有向图进行移动,不会对答案有任何影响。
性质4: 即$ SG(G)=sg(G_1) \oplus sg(G_2)...sg(G_n)$
对于所有博弈论的题目,我们只需要:
1 . 把原问题分解成几个独立的子问题,利用性质4,求出全局$ SG(G)=sg(G_1) \oplus sg(G_2)...sg(G_n)$
2 . 考虑设计一个子游戏,计算SG值,
0x01:可选步数为任意步的时候$ sg(x)=x $
0x02:可选步数为$ [1,m] $步的时候$sg(x)=x\% (m+1)$
0x03: 满足一些奇怪的性质的sg函数。
给出Nim游戏打表求SG的模板:
define MAX
/* 计算从1-n范围内的SG值。
Array(存储可以走的步数,Array[0]表示可以有多少种走法)
Array[]需要从小到大排序
*/
int SG[MAX], hash[MAX];
void GetSG(int Array[],int n = MAX-)
{
memset(SG, , sizeof(SG));
for(int i = ; i <= n; ++i)
{
memset(hash, , sizeof(hash));
for(int j = ; j <= Array[]; ++j)
{
if(i < Array[j])
break;
hash[SG[i - Array[j]]] = ;
}
for(int j = ; j <= n; ++j)
if(!hash[j])
{
SG[i] = j;
break;
}
}
}
非递归求SG
//注意 S数组要按从小到大排序表示某一堆有走法可能 SG函数要初始化为-1 对于每个集合只需初始化1遍
//n是集合s的大小 S[i]是定义的特殊取法规则的数组
int s[],sg[],n;
int SG_dfs(int x)
{
int i;
if(sg[x]!=-)
return sg[x];
bool vis[];
memset(vis,,sizeof(vis));
for(i=;i<n;i++)
{
if(x>=s[i])
{
SG_dfs(x-s[i]);
vis[sg[x-s[i]]]=;
}
}
int e;
for(i=;;i++)
if(!vis[i])
{
e=i;
break;
}
return sg[x]=e;
}
递归求SG