公平组合游戏(ICG)
定义
游戏由同样很聪明的两个人 参与,二者轮流做出决策,且都会做出最有利于自己的决策,当有一人无法做出决策时(即无法行动)游戏结束,无法做出决策的人输。 无论二者如何做出决策,游戏可以在有限步内结束。 游戏中的同一个状态不可能多次抵达。且游戏不会有平局出现。 任意一个游戏者在某一确定状态可以作出的决策集合只与当前的状态有关,而与游戏者无关。
比如:Nim游戏
Nim游戏
取 石 子 游 戏
桌子上有N堆石子,每一堆石头有ai个,两人轮流取石子。 每次只能从一堆中取出任意数目的石子,但不能不取。 取走最后一个石子者胜。
结论:
先手必败:a1 ^ a2 ^ a3 ^ … ^an = 0
那么先手必胜就是上述结果不为0
证明:
使用不严谨数学归纳法:
1.最终状态下,异或和为0
2.对于异或和不为0的状态,我们假设整体异或和为b,找到在二进制下b的最高位的1,在石头堆中找到那一位也是1的石堆a,取出一些石头,使其数量为a^b,这样整体的异或和就又成为0了
3.对于异或和为0的状态,分为两种,一种是情况1,即已经没有石头了;第二种,还有石头,那么无论怎么取,都会使整体异或和不为0(取某一堆的石头等效于让这堆石 头异或上某个不为0的数,而整体异或和为0,0异或一个不为0的数,那么结果一定不为0)
不难发现,只要双方都采取最优策略,那么先手或后手面对的局面(即异或和是否为0)会一直持续下去,直至游戏结束。
注: 对于2.中,a^b一定小于a,设 c = a ^ b,二进制下,假设b的最高位1在第k位,那么c中比k高的位数与a相同,而第k位却是0,所以不管比k小的位数是什么,c一定比a小。
SG函数
我们把每一个游戏状态抽象成一个自然数值,称之为该状态的SG函数值,且最终状态的SG函数值为0。
且定义以下递进关系:SG(u) = mex(SG(v),v为所有u的后继状态)
其中mex(minimal excludant)是一种运算,其运算结果为不在这个集合中,最小的自然数。
比如:mex(1,3,4) = 0; mex(0,1,2,4) = 3; mex(0,1,0,2) = 3。
结论:
必败态的SG值为0,必胜状态SG值不为0
证明:
同样使用不严谨数学归纳法:
1.结束状态(必败状态)的SG函数值为0
2.当前状态的SG值不为0,说明后继状态有SG函数值为0的,那么我们可以走到那个状态(即将SG为0的状态,即必败状态给对手)
3.当前状态的SG值为0,说明后继状态的SG函数函数值均不为0,那么我们无论怎么走,都会把SG函数值不为0的状态,即必胜状态给对手
例子:
考虑有一堆石头,一共有n个,两个人轮流取,每次只能取1个或者2个,不能不取,谁先取完谁赢(注意这句话与 谁先不能取谁输 等效)。
那么我们定义如下SG函数:
SG(x), 其中x为剩余的石头数量(即当前的游戏状态),
那么很显然,当x >= 2时,SG(x) = mex(SG(x-1), SG(x-2)); SG(0) = 0;SG(1) = mex(SG(0)) = 1。
那么我们可以进行运算: SG(2) = mex(SG(0), SG(1)) = 2。表示有2个石头的时候,先手必胜(与直觉相同)
SG(3) = mex(SG(2), SG(1)) = mex(2, 1) = 0。表示有3个石头的时候,先手必败。(无论先手取一个还是两个,后手都能把剩下石头取完)
SG(4) = mex(SG(3), SG(2)) = mex(0, 2) = 1。表示有4个石头的时候,先手必胜。(我们可以取1个,对手面临”先手,有3个石头的必败局面“,这与刚才证明阶段的 ”当前状态SG值不为0时,则存在后继状态的SG值为0“ 相吻合,而我们说 两个人都会做出最有利于自己的决策,就是为了保证那个人会做出”把必败局面给对手”的决策)
…
另外我们容易看出来,不同状态的SG函数可能相同(只是说明有这个现象,具体意味着什么本蒟蒻还不清楚orz)。
进阶:可以把条件改为一次可以取1,2,3个,再进行mex运算,看看结果会如何(实在是肝不动了,偷个懒)。
SG定理
应用场景:
SG定理用于多个同时进行的SG函数的游戏,就比如刚才的Nim游戏,每一堆石子,都存在自己的且与其他堆无关的SG函数。 如果把刚才例子中的一堆石子变为多堆石子,那么同样就是要利用SG定理。
内容:
假设当前游戏局面X由子游戏局面X1,X2,…Xn组成,那么SG(X) = SG(X1) ^ SG(X2) ^ … ^ SG(Xn)
(用Nim游戏举例,子游戏局面的SG值就相当于每一堆的石子的SG函数值)
证明:
我们使用”类比证明法”(当然这是不严谨的,因为本蒟蒻不会QAQ)
对于当前游戏局面的任意一个子局面的SG函数,假设为ai,那么显然表明,它的后继状态的SG值可以取0到ai-1,然后率先让所有子局面的SG值都为0的人就会胜利…..
等等!!!这不是和刚才的取石子游戏一样嘛!!!所以就能直接套用这个模板啦!!
一些启发:
所以拿到一个题目后,如果我们决定要使用SG函数来做,那么就要先分清楚子局面和后继状态:子局面内部SG函数是用mex运算,而子局面之间的运算则是异或。
例题:
POJ - 2311
题目大意:
给定一个有n * m格子的纸片,两个人轮流剪,只能沿着格子的线直线剪。率先剪出1 * 1格子的人胜利。给定n和m,问先手是否必胜。
分析:
假设当前是nm的,那么考虑它的后继状态,假设我们把它剪成 nr,n*(m-r)两个纸片,会发现,它的后继状态是由两个子状态构成的!
所以SG(n,m)的这个后继状态的SG值应该是由SG(n,r)和nSG(n,m-r)这两个子局面的SG值异或得到的
除了这种剪法,我们还有其它的剪法,而SG(n,m)的值就是对它的后继状态取mex操作后得到的。
代码:
(见代码中的标注)
0.这就是在进行各种剪法,即后继状态,而后继状态是由多个子局面组成的,所以这个后继状态的SG值是子局面的异或和。
1.这是在求mex运算,vis[x]记录的是当前状态的后继节点的SG值是否有为x的。
2.SG初始化一定不能为0,因为有的时候,SG函数的值本来就是0。
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxN = 205;
int sg[maxN][maxN];
int SG(int r, int c)
{
if(sg[r][c] != -1)
return sg[r][c];
int vis[51];
memset(vis, 0, sizeof vis);
for(int i = 2; i <= r - i; ++i)//0.
vis[SG(i, c) ^ SG(r - i, c)] = 1;
for(int i = 2; i <= c - i; ++i)
vis[SG(r, i) ^ SG(r, c - i)] = 1;
sg[r][c] = 0;
while(vis[sg[r][c]])// 1.
++sg[r][c];
return sg[c][r] = sg[r][c];
}
int main()
{
int n, m;
memset(sg, -1, sizeof sg);//2.
while(~scanf("%d%d", &n, &m)) {
if(SG(n, m) > 0)
printf("WIN\n");
else
printf("LOSE\n");
}
return 0;
}
希望蒟蒻的这篇博客对你有帮助orz