此文为以下博客做的摘要:
https://blog.csdn.net/strangedbly/article/details/51137432
----------------------------------------------------------------------------------------
1、定义P-position和N-positon
P表示Previous,N表示Next。
即上一个移动的人有必胜策略的局面是P-position,“先手必败”或“后手必胜”,现在移动的人有必胜策略的局面是N-positon,“后手必败”或“先手必胜”。下面为更严谨的定义:
1、无法进行任何移动的局面(即terminal position)是P-position;
2、可以移动到P-position的局面是N-position;
3、所有移动都导致N-position的局面是P-position。
根据定义,不重现局面,那么positions的集合可以进行拓扑排序,并且能通过定义判断为P-position还是N-position。
2、Nim游戏及Bouton‘s Theorem
Bouton‘s Theorem :对于一个Nim游戏的局面(a1, a2, ...an),它是P-position当且仅当 a1^a2^...^an = 0,其中 ^ 表示异或运算。
要证明这个定理,基本上是证明这种运算符合上述position的定义。即证明三个命题:1、这个判断将所有无法移动的局面判为P-position;2、被判为N-position的局面一定可以移动到某个P-position;3、被判为P-position的局面无法移动到某个P-position。
第一个命题:无法移动的局面只有全为0,即异或仍为0。
第二个命题:对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0,一定存在某个合法的移动,将ai改变成ai‘后满足a1^a2^...^ai‘^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai‘=ai^k,此时a1^a2^...^ai‘^...^an=a1^a2^...^an^k=0。
第三个命题:对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai‘后满足a1^a2^...^ai‘^...^an=0。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai‘^...^an可以得到ai=ai‘。所以将ai改变成ai‘不是一个合法的移动。证毕。
3、Sprague-Grundy函数
首先给出这个函数适用范围,貌似所有博弈的题目都是符合这个范围。现在来研究似乎更为一般的游戏:给定一个有向无环图和一个其实顶点上的一枚棋子,两名选手交替将这棋子沿有向边移动,无法移动者判负。事实上,这个游戏可以认为是所有Impartial Combinatorial Games的抽象模型。即任何一个ICG都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Garundy函数g如下:g(x)=mex{ g(y) | y是x的后继 }。
来看一下SG函数的性质。首先,所有的terminal position所对应的顶点,也就是没有出边的顶点,其SG值为0,因为它的后继集合是空集。下面为原作图
(pic1)
对于一个g(x) = 0的顶点x,它的所有后继 y 都满足 g(y) != 0。
对于一个g(x) != 0的顶点,必定存在一个后继 y 满足 g(y) = 0。
即顶点 x 代表的position为P-position,当且仅当 g(x) = 0。相应地N-position为 g(x) != 0。所以有向无环图的每个顶点SG值能够计算出来,就能找到每种局面的必胜策略。但SG函数的用途远没有这么简单。如果将有向图游戏变复杂,比如,有向图上不止有一枚棋子,而是有n枚,每次可以人选一颗移动,这时怎么找必胜策略?
考虑一下顶点的SG值的意义。当g(x) = k时,表明对于任意一个 0 <= i < k,都存在x的一个后继 y 满足g(y) = i。即当某棋子的SG值为k时,我们可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。这类似于Nim游戏,Nim游戏的规则就是:每次选择一堆数量为k的石子,可以把它变成0、变成1、……、变成k-1,但绝对不能保持k不变。这表明,如果将n枚棋子所在的顶点的SG值看作n堆相应数量的石子,那么这个Nim游戏的每个必胜策略都对应于原来这n枚棋子的必胜策略!
对于n个棋子,设它们对应的顶点的SG值分别为(a1,a2,...,an),再设局面(a1,a2,...,an)时的Nim游戏的一种必胜策略是把ai变成k,那么原游戏的一种必胜策略就是把第i枚棋子移动到一个SG值为k的顶点。其实我们还是只要证明这种多棋子的有向图游戏的局面是P-position当且仅当所有棋子所在的位置的SG函数的异或为0。这个证明与上面的Bouton‘s Theorem几乎是完全相同的,只需要适当的改几个名词就行了。
刚才,为了使问题看上去更容易一些,认为n枚棋子是在一个有向图上移动。但如果不是在一个有向图上,而是每个棋子在其对应的有向图上,每次可以任选一个棋子(也就是任选一个有向图)进行移动,这样也不会给结论带来任何变化。
所以我们可以定义有向图游戏的和(Sum of Graph Games):设G1、G2、……、Gn是n个有向图游戏,定义游戏G是G1、G2、……、Gn的和(Sum),游戏G的移动规则是:任选一个子游戏Gi并移动上面的棋子。Sprague-Grundy Theorem就是:g(G)=g(G1)^g(G2)^...^g(Gn)。也就是说,游戏的和的SG函数值是它的所有子游戏的SG函数值的异或。
任何一个ICG都可以抽象成一个有向图游戏。所以“SG函数”和“游戏的和”的概念就不是局限于有向图游戏。我们给每个ICG的每个position定义SG值,也可以定义n个ICG的和。所以说当我们面对由n个游戏组合成的一个游戏时,只需对于每个游戏找出求它的每个局面的SG值的方法,就可以把这些SG值全部看成Nim的石子堆,然后依照找Nim的必胜策略的方法来找这个游戏的必胜策略了!(Nim其实就是n个从一堆中拿石子的游戏求SG的变型,总SG=n个sg的异或)。(very important)
有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗……我们可以把它看作3个子游戏,第1个子游戏只有一堆石子,每次可以取1、2、3颗,很容易(看下图pic2)看出x%4==0时处于P局面,即x颗石子的局面的SG值是x%4,(即把pic2中的值改成原值%4)。第2个子游戏也是只有一堆石子,每次可以取奇数颗,经过简单的画图可以知道这个游戏有x颗石子时的SG值是x%2。第3个游戏有n-2堆石子,就是一个Nim游戏。对于原游戏的每个局面,把三个子游戏的SG值异或一下就得到了整个游戏的SG值,然后就可以根据这个SG值判断是否有必胜策略以及做出决策了。其实看作3个子游戏还是保守了些,干脆看作n个子游戏,其中第1、2个子游戏如上所述,第3个及以后的子游戏都是“1堆石子,每次取几颗都可以”,称为“任取石子游戏”,这个超简单的游戏有x颗石子的SG值显然就是x。其实,n堆石子的Nim游戏本身不就是n个“任取石子游戏”的和吗?
(pic2)
所以,对于我们来说,SG函数与“游戏的和”的概念不是让我们去组合、制造稀奇古怪的游戏,而是把遇到的看上去有些复杂的游戏试图分成若干个子游戏,对于每个比原游戏简化很多的子游戏找出它的SG函数,然后全部异或起来就得到了原游戏的SG函数,就可以解决原游戏了。
以下是本文最重要的部分:
解题模型:
1.把原游戏分解成多个独立的子游戏,则原游戏的SG函数值是它的所有子游戏的SG函数值的异或。
即sg(G)=sg(G1)^sg(G2)^...^sg(Gn)。
2.分别考虑没一个子游戏,计算其SG值。
SG值的计算方法:(重点)
1.可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);
2.可选步数为任意步,SG(x) = x;
3.可选步数为一系列不连续的数,用模板计算。
模板1:打表
1 //f[], 可以取走的石头个数 2 //sg[],SG函数值 3 //mex[],mex{} 4 int f[MAXN], sg[MAXN], mex[MAXN]; 5 void getSG(int n) 6 { 7 int i, j; 8 memset(sg, 0, sizeof(sg)); 9 for(i = 1;i <= n;++i) 10 { 11 memset(mex, 0, sizeof(mex)); 12 for(j = 1;f[j] <= i;++j) 13 mex[sg[i - f[j]] = 1; 14 for(j = 0;j <= n;++j) 15 if(mex[j] == 0) 16 { 17 sg[i] = j; 18 break; 19 } 20 } 21 }
模板2:DFS
//s[i] 数组要从小排到大,SG函数要初始化为 -1, 每个集合只需初始化一遍 //n是集合s 的大小,s[i]是定义的特殊取法规则的数组 int s[105], sg[10005], n; //memset(sg, -1, sizeof(sg)); //sort(s, s + n); int SG_dfs(int x) { int i; if(sg[x] != -1) return sg[x]; bool vis[105]; memset(vis, 0, sizeof(vis)); for(i = 0;i < n;++i) { if(x >= s[i]) { SG_dfs(x - s[i]); vis[sg[x - s[i]] = 1;//mex{} } } int e; for(i = 0;;++i) if(!vis[i]) { e = i; break; } return sg[x] = e; }
首选打表,实在不行才用DFS。
--------------------------------------------------------------------------------------------
原博主下面还有题,另外也开了一篇写题的博客
原文地址:https://www.cnblogs.com/shuizhidao/p/9271712.html