从一个问题进入。
今天我们要认识一对新朋友,Alice与Bob。
Alice与Bob总是在进行各种各样的比试,今天他们在玩一个取石子的游戏。
在这个游戏中,Alice和Bob放置了N堆不同的石子,编号1..N,第i堆中有Ai个石子。
每一次行动,Alice和Bob可以选择从一堆石子中取出任意数量的石子。至少取1颗,至多取出这一堆剩下的所有石子。
Alice和Bob轮流行动,取走最后一个石子的人获得胜利。
假设每一轮游戏都是Alice先行动,请你判断在给定的情况下,如果双方都足够聪明,谁会获得胜利?
这是一个古老而又经典的博弈问题:Nim游戏。
Nim游戏是经典的公平组合游戏(ICG),对于ICG游戏我们有如下定义:
对于第三条,我们有更进一步的定义Position,我们将Position分为两类:
它们有如下性质:
算法实现
取子游戏算法实现——
步骤1:将所有终结位置标记为必败点(P点);
步骤2: 将所有一步操作能进入必败点(P点)的位置标记为必胜点(N点)
步骤3:如果从某个点开始的所有一步操作都只能进入必胜点(N点) ,则将该点标记为必败点(P点) ;
步骤4: 如果在步骤3未能找到新的必败(P点),则算法终止;否则,返回到步骤2。
在这个游戏中,我们已经知道A[] = {0,0,…,0}的局面是P局面,那么我们可以通过反向枚举来推导出所有的可能局面,总共的状态数量为A1∗A2∗…∗AN。并且每一次的状态转移很多。
虽然耗时巨大,但确实是一个可行方法。
当然,我们这里会讲这个题目就说明肯定没那么复杂。没错,对于这个游戏有一个非常神奇的结论:
对于一个局面,当且仅当A1 xor A2 xor … xor AN =0时,该局面为P局面。
如何证明这个结论呢?
同样从一个问题进入。
给定一个有向无环图和一个起始顶点上的一枚棋子,Alice和Bob交替的将这枚棋子沿有向边进行移动,无法移动者判负。问是否有必胜策略。
事实上,这个游戏可以认为是所有ICG游戏的抽象模型。也就是说,任何一个ICG游戏都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。下面我们就在有向无环图的顶点上定义SG(Sprague-Garundy)函数。
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的SG函数sg如下:sg(x)=mex{ sg(y) | y是x的后继 }。也就是说,一个点的SG函数为在它所有后继中都未出现的最小的值。
来看一下SG函数的性质。首先,所有的没有出边的顶点,其SG值为0,因为它的后继集合是空集。然后对于一个sg(x)=0的顶点x,它的所有后继y都满足 sg(y)≠ 0。对于一个sg(x)≠ 0的顶点,必定存在一个后继y满足sg(y)=0。
这个时候你就应该有所发现了!SG函数的性质和N,P局面的性质非常相似!
以上表明,顶点x所代表的postion是P-position当且仅当sg(x)=0(跟P-positioin/N-position的定义是完全对应的)。
举个栗子
例如:取石子问题,有1堆n个的石子,每次只能取{1,3,4}个石子,先取完石子者胜利,那么各个数的SG值为多少?
sg[0]=0,f[]={1,3,4},
x=1时,可以取走1-f{1}个石子,剩余{0}个,mex{sg[0]}={0},故sg[1]=1;
x=2时,可以取走2-f{1}个石子,剩余{1}个,mex{sg[1]}={1},故sg[2]=0;
x=3时,可以取走3-f{1,3}个石子,剩余{2,0}个,mex{sg[2],sg[0]}={0,0},故sg[3]=1;
x=4时,可以取走4-f{1,3,4}个石子,剩余{3,1,0}个,mex{sg[3],sg[1],sg[0]}={1,1,0},故sg[4]=2;
x=5时,可以取走5-f{1,3,4}个石子,剩余{4,2,1}个,mex{sg[4],sg[2],sg[1]}={2,0,1},故sg[5]=3;
以此类推…..
x 0 1 2 3 4 5 6 7 8....
sg[x] 0 1 0 1 2 3 2 0 1....
过程
1.把原游戏分解成多个独立的子游戏,则原游戏的SG函数值是它的所有子游戏的SG函数值的异或。
2.分别考虑没一个子游戏,计算其SG值。
关于SG(x) = x % (m+1);和SG(x) = x;
模板
两种方法我觉得都是搜索
方法一:打表
f[]可以取走的石子个数,注意f[]需要从小到大排序
sg[]SG函数值;vis[]标记数组,用于求mex{}
int f[MAXN],sg[MAXN];
bool vis[MAXN];
void getSG(int n)
{
sort(f+1,f+1+n);
memset(sg,0,sizeof(sg));
for (int i=1; i<=n; i++)
{
memset(vis,0,sizeof(vis));
for (int j=1; f[j]<=i; j++)//f排序是为了让每一种取法都循环到
vis[sg[i-f[j]]]=1;
for (int j=0; j<=n; j++)
{
if (vis[j]==0)
{
sg[i]=j; break;
}
}
}
}
方法二:dfs
s数组是定义特殊取法规则的数组,注意要按照从小到大排序;n表示集合大小
SG函数要初始化为-1,每个集合只需初始化一遍
int s[MAXN],sg[MAXN],n;
bool vis[MAXN];
int SG_dfs(int x)
{
if (sh[x]!=-1)
return sg[x];
memset(vis,0,sizeof(vis));
for (int i=0; iif (x>=s[i])
{
SG_dfs(x-s[i]);
vis[sg[x-s[i]]]=1;
}
}
int i=0;
while (1)
{
if (!vis[i])
return sg[x]=i;
i++;
}
}
SG函数与Nim游戏的联系?
如果对于一个游戏,它是ICG。那么我们可以发现,对于此游戏的一种局面,ICG上的多个sg函数的值,如果看做多个石堆的话,就相当于一个nim游戏!我们来一步步推导:
通过这个sg函数,我们就可以把所有ICG游戏转换成nim游戏!
第一个当然是Nim game;
这也是十分经典的一类题,并且有许多变形
这种问题只需要考虑奇数的位置进行Nim游戏,因为石子在偶数位置是可以模仿操作的。
为什么呢?因为任何人移动了偶数层的石子后,另外一个人总是可以把他们再移到下一奇数层,那么奇数层拿到偶数层的石子就相当于是丢掉了。
所以就变成了只有奇数层的Nim游戏。
可以看做独立的问题,使用sg函数求mex
(为什么可以看做独立的问题?)
取完最后的石子的为败者。
策略:当至少有两堆的石子数大于1时,拿法跟常规的Nim一样。当对手走完某一步后只剩一堆石子数大于1时,将那一堆石子变成0或者1,从而保证有奇数个石子数为1的堆。原因是:在nim游戏中最优策略永远不会让你留下一个石子数大于1的堆(否则Nim和一定不为0),而且有两堆石子数大于1的时候,对手也无法使这两堆同时消失。所以游戏最终变成只有一堆石子大于1的时候一定是轮到你了。
结论:先手必胜当且仅当:
(1)每一堆都只剩一个石子,且SG为0.(即:有偶数堆的石子数目为1)
(2)至少有一堆石子数目>1,且SG和不为0.(先手总可以将数目>1的石子堆数变为奇数堆)
证明:
(1)情况一:有 N 个堆,每个堆只有一个石子。显然,先手必胜当且仅当 N 为偶数,即SG和为0。
(2)情况二:
①当 SG 不为 0 时:若还有至少两堆石子的数目大于 1,则先手将 SG 值变为 0 即可;若只有一堆石子数大于 1,则先手总可以将状态变为只有奇数个 1。所以,当 SG 不为 0 时先手必胜。
②当 SG 为 0 时:至少有两堆石子的数目大于 1,则先手决策完之后,必定至少有一堆的石子数大于 1,且 SG 值不为 0(原先为0,改变一个数后一定不为0了),由上段的论证我们可以发现, 此时,无论先手如何决策,都只会将游戏带入N态(即情况1),所以先手必败。
各类博弈问题的总结
HNOI2007 分裂游戏
BZOJ 3895
谢鸣:
xiaoyimi张浩南
zfy2000学姐
参考文献:
IOI2009国家集训队论文 贾志豪《组合游戏略述——浅谈SG游戏的若干拓展及变形》
http://blog.csdn.net/Clove_unique/article/details/53868567
https://www.cnblogs.com/frog112111/p/3199780.html
http://www.cnblogs.com/MyNameIsPc/p/7597366.html
http://blog.csdn.net/qiankun1993/article/details/6765688
(未完)