基于笔者之前对于几种二元零和博弈游戏的介绍,这里将其思想进行简单的提炼,并引出解决这类二元零和博弈游戏的强大工具——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. Java 6 Thread States and Life Cycle.

    Ref: Java 6 Thread States and Life Cycle This is an example of UML protocol state machine diagram sh ...

  2. ASP.NET mvc 遇见的问题

    1.数据库配置 The specified named connection is either not found in the configuration, not intended to be ...

  3. kvc简单实现

      除了一般的赋值和取值的方法,我们还可以用Key-Value-Coding(KVC)键值编码来访问你要存取的类的属性 kvc: kvc    key value coding 键值对编码 可以通过 ...

  4. node http.request请求

    var http = require('http'); var querystring = require('querystring'); var path = '/cricket/getRecord ...

  5. hdu 4502吉哥系列故事——临时工计划 (简单DP)

    Problem Description 俗话说一分钱难倒英雄汉,高中几年下来,吉哥已经深深明白了这个道理,因此,新年开始存储一年的个人资金已经成了习惯,不过自从大学之后他不好意思再向大人要压岁钱了,只 ...

  6. Codeforces 475 D.CGCDSSQ

    题目说了a的范围小于10^9次方,可实际却有超过的数据...真是醉了 算出以f[i]结尾的所有可能GCD值,并统计: f[i]可以由f[i-1]得出. /* 递推算出所有GCD值,map统计 */ # ...

  7. 【POJ1823】【线段树】Hotel

    Description The "Informatics" hotel is one of the most luxurious hotels from Galaciuc. A l ...

  8. JQuery树形目录制作

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DT ...

  9. 快速访问WCF服务--ServiceModel 元数据实用工具 (Svcutil.exe)

    基本定义 ServiceModel 元数据实用工具用于依据元数据文档生成服务模型代码,以及依据服务模型代码生成元数据文档. SvcUtil.exe ServiceModel 元数据实用工具可在 Win ...

  10. uboot总结:uboot配置和启动过程3(config.mk分析)

    说明:文件位置:在uboot的目录下,文件名为:config.mk.是一个makefile文件,以后会被主Makefile调用. 它的主要作用的是: (1)具体的设置交叉编译工具链接(主Makefil ...