VG 的博弈系列学习笔记(1):浅谈 Wythoff 游戏

· · 个人记录

由于一些原因,此文仅保留 Wythoff 游戏部分,其余部分转移至此。

游戏介绍

威佐夫(Wythoff)游戏,又称威佐夫博弈,是博弈论中的著名游戏之一。

游戏内容:两堆石子,一堆 a 个,一堆 b 个,两位游戏者轮流操作,每次操作可以从一堆中取任意颗石子或者从两堆取同样多石子。谁不能再取,就算谁输。

游戏解法

Wythoff 游戏的解法,就是求出先手能否获胜。以下用整数对 (a,b) 表示两堆石子个数是 ab 的情况。由于 (a,b)(b,a) 是一种情况,所以此处只考虑 a \le b 的情况。

显然, (0,0) 时先手必败。接下来的一个必败情况就是 (1,2)。再往下,就得到 (3,5)。然后是 (4,7),(6,10),(8,13),(9,15),...

我们设第 i 大的必败情况为 (a_i,b_i)

根据规律,发现 a_i=\operatorname{mex}(a_1,b_1,a_2,b_2,...,a_{i-1},b_{i-1})\operatorname{mex}(S) 为集合 S 中最小未出现整数,下同), b_i=a_i+i

规律证明

现在证明一个必败状态后继状态不可能是另一个必败状态。

因为 a_i 在前面没有出现过,所以 a_i>a_{i-1}。所以 b_i=a_i+i>a_{i-1}+i-1=b_{i-1}。所以为了使这个情况转化为另一个必败状态,必然要从两堆各取出同样多的石子。所以 a,b 差不变,而其余必败状态 a_j,b_jj 均与 a_ib_i 的差 i 不相等。所以它的所有后继状态均不是必败状态。

下面证明一个必胜状态必有一个后继状态是必败状态。

a>a_k,b=b_k 时,只需从 a 堆取出 a-a_k 个即可。 a=a_k,b>b_k 时亦然。

当 $a=a_{k+l}$ 时,若 $b>b_{k+l}$。则如上可证。否则,因为 $b \neq b_{k+l}$,所以 $b<b_{k+l}$。若 $b=b_m$,则满足 $a>a_m,b=b_m$,如上。若 $b=a_m$,因为 $b>a$,所以 $m>k+l$,所以 $0<a_m-a_{k+l}<k+l$,所以将 $b$ 堆取到 $b_{a_m-a_{k+l}}$ 时,a堆将取到 $a_{a_m-a_{k+l}}$ 颗,为一个必败状态。 综上,必胜状态必有一个后继状态是必败状态。 必败状态通项公式:设当前状态为 $(a,b)(a<b)$,则当 $a=\lfloor\frac{\sqrt{5}+1}{2}\rfloor\times(b-a)$ 时该状态为必败状态。 #### 有趣的性质 首先,我们定义 $a_{i+1}-a_i=c_i,b_{i+1}-b_i=d_i$。然后,有 $c_0=a_1=1,d_0=b_1=2$。我们发现, $d_i=c_i+1$。 每当 $d_i=2$ 时, $c_i$数组对应在最后添上一个 $2$;每当 $d_i=3$ 时, $c_i$数组对应在最后添上一个 $1$ 和一个 $2$。递推大概就是这个样子: ```cpp for(int i=0;;i++)//c,d是vector动态数组 { d[i]=c[i]+1; if(d[i]==2)c.push_back(2); if(d[i]==3)c.push_back(1),c.push_back(2); } ``` 这样就可以求出 $c_i$ 和 $d_i$,然后推出 $a_i,b_i$ 了。 #### Wythoff 和 Fibonacci 那些事 往著名的 Fibonacci 数列 $f_i$ 中,代入 Wythoff 的 $a_i,b_i$ 会出现神奇的事情。 有: $a_{f_i}=f_{i+1}-i \bmod 2$, $b_{f_i}=a_{f_i}+f_i=f_{i+2}-i \bmod 2$。(此处 $f_i$ 数列为 $1,2,3,5,8,\dots$ 且 $i>1$)。 关于 Wythoff 博弈就讲这么多。 两道 Wythoff 博弈的习题:[nyoj161 取石子(四)](http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=161)&[洛谷P2252取石子游戏](https://www.luogu.org/problemnew/show/P2252) 代码P2252: ```cpp #include<cmath> #include<cstdio> using namespace std; long long a,b; int main() { scanf(" %lld %lld",&a,&b); if(a>b) { long long t=a;a=b;b=t; } long long c=b-a; int d=((sqrt(5.0)+1.0)/2.0*c); if(a==d)printf("0"); else printf("1"); return 0; } ```