基于笔者之前对于几种二元零和博弈游戏的介绍,这里将其思想进行简单的提炼,并引出解决这类二元零和博弈游戏的强大工具——SG函数。

其实对于博弈游戏如Bash、Nim等基本类型,异或一些比较高级的棋类游戏例如井字棋、中国象棋、华容道等,可以说它们是同质的。

我们先从比较高的角度来看待如何得到博弈当中最优的策略,这其实也是笔者认为解决简单的二元零和博弈、理解SG函数和写棋类AI关键所在。博弈是一个大量状态之间进行转移的过程,我们将每种状态视为一个节点,这种转移关系视为一种有向边,那么我们容易构建起博弈树(在SG函数中我们常称“游戏树”),我们是从结局(即叶节点,因为它的胜负态一目了然)往前搜索,然后逆着正常决策的过程,标记每种局面的胜负态,然后来帮助我们在按照正常决策顺序的时候,选出最优策略。对于华容道、五子棋、井字棋这类游戏,需要根据当前出现的局面,然后模拟出接下来可能出现的所有情况,然后评估棋局并回溯回来标记出最优策略。同样对于二元零和博弈,也是相同的道理。只不过二元零和博弈非常简单,其胜负态(我们用N标记面临当前局面的人有必胜策略,P是必败策略)常常会呈现出规律化的分布(这就是很多人常说的“找规律”,但是它没有体现“找规律”这种方法和博弈本身的关系),因此我们能够设计线性的算法来判断其胜负态。

我们来举个例子来理解一下这个过程:

ex1:给出一个nxm棋盘,将棋子从(1,m)开始移动,要求只能向左、下、左下移动,不能移动棋子的人输,请问分析这个游戏的必胜态分布。(Problem source :hdu 2147 )

分析:对于这个问题,我们按照上面我们给出解决博弈问题的通法,从结局开始分析。

对于这个矩阵(显然棋局可以看成矩阵),我们从(n,1)这个状态开始构建NP分布图,以4x4的矩阵为例,我们容易得到如下的NP分布图。

N N N N

P N P  N

N N N N

P N P  N

规律就一目了然了。

那么下面我们通过一个具体的问题来引出解决这一类二元零和博弈问题的通解——sg函数。其实从本质上讲,它就是一个打NP表的有力工具。

ex2:给出三个石子堆的数目m、n、p,两个游戏玩家每次只能拿取斐波那契数个石子,最终没有石子取得人输,如何分析胜负态的分布?(Problem source : hdu 1848)

有nim博弈基础读者会注意到,这其实是单堆nim的推广形式,既然是推广形式,就应该使用推广方法,即sg函数。

上文提到,任何博弈都可以看成多个状态之间的转移,我们将每种状态视为一个顶点,而状态之间的转移视为点与点的有向边,这样我们容易建立起无环有向树,也就是我们常说的博弈树或者是状态树,而我们判断胜负态分布的关键就是从胜负态显然的叶节点开始,然后往根部构造sg函数。

整体的思路明了了,我们如何具体的计算sg函数呢?

这里定义一个运算符mex,对于整数集合mex(S),mex(S)的值是S集合中没有出现的最小负整数,那么对于sg(x),它记录着状态参数x(玩家当前面临单堆石子的剩余数)对应的胜负态,对于它的计算,有如下递推定义:

sg(x) = mex({sg(y)|y∈son(x)}),其中son(x)表示状态树中状态参量x的儿子所有节点构成的集合。

递推计算式给出的貌似有些唐突,我们从叶节点开始尝试模拟,对于x = 0的叶节点,按照上述定义,显然有sg[0] = 0.它其实对应着P态。而对于它的父节点x1,我们根据递推定义,sg(x1)必然不为0,也很容易理解它对应着N态,结合二元零和博弈胜负态的交替规律(这个规律很多资料中视为定理进行表述,对于理解整个决策过程非常重要),我们能够得到整个博弈树顶点的权值,即sg函数。

我们还会得到结论,对于状态x,sg(x) = 0,面临这个状态参量x的游戏者必败。(可见很多资料往往“先手必胜”的说法并不准确。)

当sg(x) != 0,面临这个状态参量x的游戏者必胜。

其实可能有读者已经会疑惑了,这里sg(x)储存的值其实只有两种形态(0和非0),那么我们储存那些非零的数还有什么意义呢?

刚好这与我们下面要解决的问题是呼应的。

上文讨论了单堆取石子游戏的推广形式如何用sg函数来解决,那么对于ex2中的三堆含m、n、p个的石子(称为多个sg函数组合起来的组合游戏),基于对运算符mex和sg函数自身内涵的理解,面对sg(m) = k,我们可将其视为从含k个石子堆拿出任意数量的游戏操作(有人可能会质疑是否会面临sg(m) = mex({0,1,2,...,k,k+2})这种使得转化不等价的局面,其实可以实践一下,对于状态m是否会出现sg(y) = k + 2)。那么取石子游戏的推广类型就利用sg函数得到了完美解决.

简单的参考代码如下:

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = ;
int sg[maxn] , flag[maxn];
int f[]; void get_sg(int n)
{
sg[] = ;
for(int i = ;i <= n;i++)
{ memset(flag , , sizeof(flag));
for(int j = ;i - f[j] >= ;j++)
flag[sg[i - f[j]]] = ;
for(int k = ;;k++)
{
if(flag[k] == )
{
sg[i] = k;
break;
}
}
}
}
int main()
{
f[] = , f[] = ;
for(int i = ;i <= ;i++)
{
f[i] = f[i-] + f[i-];
// printf("%d\n",f[i]);
}
get_sg();
int m , n , p;
while(scanf("%d%d%d",&m,&n,&p) != EOF)
{
if(n == || m == || p == )
break;
if((sg[m]^sg[n]^sg[p]) == ) //注意位运算和==运算符的优先级
printf("Nacci\n");
else
printf("Fibo\n");
}
}

从SG函数浅谈解决博弈问题的通法的更多相关文章

  1. hdu 1847(SG函数,巴什博弈)

    Good Luck in CET-4 Everybody! Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ...

  2. 从 A/Looper: Could not create epoll instance. errno=24 错误浅谈解决各种 bug 的思路

    今天代码写着写着就莫名闪退了,手机也没有“程序停止运行”的提示,logcat也没有看到蓝色的调用栈log,这样的闪退最是蛋疼了,还好必现.复现几次之后,终于从logcat中看到了一行可疑的log: A ...

  3. Javascript-回调函数浅谈

    回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数.回调函数不是由该函数的实现方直接调用,而是在特定 ...

  4. C# eval()函数浅谈

    <%# Bind("Subject") %> //绑定字段 <%# Container.DataItemIndex + 1%> //实现自动编号<%# ...

  5. 继承虚函数浅谈 c++ 类,继承类,有虚函数的类,虚拟继承的类的内存布局,使用vs2010打印布局结果。

    本文笔者在青岛逛街的时候突然想到的...最近就有想写几篇关于继承虚函数的笔记,所以回家到之后就奋笔疾书的写出来发布了 应用sizeof函数求类巨细这个问题在很多面试,口试题中很轻易考,而涉及到类的时候 ...

  6. Sql Server存储过程和函数浅谈

    今天给大家总结一下sql server中的存储过程和函数.本人是小白,里面内容比较初级,大神不喜勿喷 自行飘过就是.. 首先给大家简单列出sql server中的流控制语句,后面会用到的^_^ sql ...

  7. 从Java继承类的重名static函数浅谈解析调用与分派

    在java中,static成员函数是否可以被重写呢? 结论是,你可以在子类中重写一个static函数,但是这个函数并不能像正常的非static函数那样运行. 也就是说,虽然你可以定义一个重写函数,但是 ...

  8. Hash函数浅谈

    Hash函数是指把一个大范围映射到一个小范围.把大范围映射到一个小范围的目的往往是为了节省空间,使得数据容易保存. 除此以外,Hash函数往往应用于查找上.所以,在考虑使用Hash函数之前,需要明白它 ...

  9. 【转】博弈问题及SG函数(真的很经典)

    博弈问题若你想仔细学习博弈论,我强烈推荐加利福尼亚大学的Thomas S. Ferguson教授精心撰写并免费提供的这份教材,它使我受益太多.(如果你的英文水平不足以阅读它,我只能说,恐怕你还没到需要 ...

随机推荐

  1. get 和 post的使用.

    Two commonly used methods for a request-response between a client and server are: GET and POST. GET  ...

  2. vs编译和运行的区

    编译: 是把代码转变成一系列指令(把源代码翻译为计算机能够识别的语言),产生目标代码,并不限于EXE(EXE只是WINDOWS的东西),这样才能装入内存; 运行: 是运行目标代码(运行EXE),就是执 ...

  3. Oracle 左连接、右连接、全外连接、(+)号作用、inner join(等值连接) (转载)

    Oracle  外连接 (1)左外连接 (左边的表不加限制)       (2)右外连接(右边的表不加限制)       (3)全外连接(左右两表都不加限制) 外连接(Outer Join) oute ...

  4. IOS学习--UILable使用手册(20150120)

    第一步:创建一个UILable对象 UILabel *lable = [[UILabel alloc]initWithFrame:CGRectMake(, , , )]; 第二步:设置对象的各种属性 ...

  5. iOS 原生二维码扫描(可限制扫描区域)

    篇文章的主要原因不是展示如何使用  AVFoundation 来进行二维码扫描,更主要的是限制扫描二维码的范围.(因为默认的是全屏扫描) 项目遇到扫描二维码的功能需求,这里我放弃了使用三方库,而采用了 ...

  6. iOS开发UI篇——Button基础

    一.简单说明 一般情况下,点击某个控件后,会做出相应反应的都是按钮 按钮的功能比较多,既能显示文字,又能显示图片,还能随时调整内部图片和文字的位置 二.按钮的三种状态 1. normal(普通状态) ...

  7. 安卓学习之ListView和GridView

    ListView 和 GridView是安卓中显示信息的两个很基本也最常用的控件.他们的用法很相似,但是他俩也是有区别的. ListView显示的数据会将他的item放在一行显示,而且根据内容给出it ...

  8. Python:运算符

    #!/usr/bin/python3 #运算符 #算术运算符 print("算术运算符:","+ - * / % **(幂) //(取整)") #比较运算符 p ...

  9. phpcms V9 数据模型基类(转)

    转自:http://www.cnblogs.com/Braveliu/p/5100421.html 在学习<phpcms V9首页模板文件解析>的第七步,我们看到content_model ...

  10. php开发利器

    phpstorm 当前版本2016.1 之前用的为Zend studio,比之notepad++确实方便很多,不过很多方面还是不方便的,比如定位文件,上传下载到svn什么的. 看到phpstorm新版 ...