基于笔者之前对于几种二元零和博弈游戏的介绍,这里将其思想进行简单的提炼,并引出解决这类二元零和博弈游戏的强大工具——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. Bat命令知识[转]

    基础部分: 一.基础语法: 1.批处理文件是一个".bat"结尾的文本文件,这个文件的每一行都是一条DOS命令.可以使用任何文本文件编辑工具创建和修改. 2.批处理是一种简单的程序 ...

  2. 浅谈Android系统的图标设计规范

    http://homepage.yesky.com/89/11620089.shtml 目前移动平台的竞争日益激烈,友好的用户界面可以帮助提高用户体验满意度,图标Icon是用户界面中一个重要的组成部分 ...

  3. 使用Sqlite数据库存储数据

    1.Sql基本命令 1.1.创建表 表是有行和列组成的,列称为字段,行称为记录. 使用CREATE命令来创建表: 1 CREATE TABLE tab_student (studentId INTEG ...

  4. MSSQL批量替换网址字符串语句

    1.如何批量替换ntext字段里面的数据 问题描述: 我想把数据库中News表中的字段content中的一些字符批量替换. 我的content字段是ntext类型的. 我想替换的字段是content字 ...

  5. Netbeans IDE配置

  6. CI 笔记(1)

    1. 下载CI,官方网站,目前3.x版本已经更新,2.2.6版本为2.x版本的最后的一个版本.为了和视频教材一致,使用CI 2.x版本 2. 目录结构,从application里面的,controll ...

  7. ScheduleThreadPoolExecutor源码分析(二)

    DelayedWorkQueue: DelayedWorkQueue实现了BlockingQueue接口,因此其可以作为线程池的任务队列.BlockingQueue的主要属性有以下几个: privat ...

  8. mem 族函数的实现

    1.void * memcpy ( void * dest, const void * src, size_t num ); 头文件:#include <string.h>memcpy() ...

  9. Perl 中级教程 第5章课后习题

    5. 9. 1. 练习1 [5 分钟] 先不要运行程序, 看看你能否判断出这程序的哪部份出了问题?如果你看不出 来, 就可以运行一相程序, 得到些暗示, 来看是否能修改好: my %passenger ...

  10. ubuntu 升级命令

    apt-get update && apt-get dist-upgrade