在算法竞赛中,博弈论题目往往是以icg。通俗的说就是两人交替操作,每步都各自合法,合法性与选手无关,只与游戏有关。往往我们需要求解在某一个游戏或几个游戏中的某个状态下,先手或后手谁会胜利的问题。就比如经典的:几堆石子,两人可以分别拿若干个,一次只能选择一个石子堆操作,问给定状态下,先手胜利还是后手胜利?

  而nim与sg函数就是对于这类问题的解法,在我的理解看来,sg函数和nim分别对应不同阶段的决策:前者对于单个游戏决策,后着是将这些单个游戏综合起来的整体决策

一、状态与转移

  icg游戏往往可以分为两个部分,规则与局面。而这两个分别对应了转移与状态。规则决定了状态转移的合法性,状态是转移的基本。

  什么是状态?状态是一个静态的局面。就好比当下棋时的局面一样。在游戏的每个阶段,不论是开始,中间,或是结束。每一个局面都对应了一种状态。

  什么是状态的转移?单个分开的局面无法构成一个完整的游戏,所以就需要从某一个状态转移到另一个状态,来进行一次操作。

  举个例子:有5个石子放在一堆。

        5个石子就是一种状态,在不受限制下,你可以改变这个状态。

        例如:取走4个石子。就是将5个石子这个状态转移到1个石子这个状态,操作就是取走4个石子。

        而这个操作的合法性取决于游戏的规则。

        例如:一次最多取3个石子。 那么上条操作取4个石子就是一次不合法的操作,意味着你不能从5这个状态直接转移到1这个状态。

      那么对于5个石子,每次最多取三个,从中我们可以得到如下状态转移的图(有向)

          

二、sg函数

   概念

   首先,引入mex值的概念。mex是指不属于集合中整数的最小正整数。而sg值就是不属于后继集合的最小正整数。

   例如上图中:0没有后继元素  所以最小正整数为0,sg(0)=0;

         1后继元素只有0,不属于后继集合的最小正整数为1,sg(1)=1;

         同理可得sg(2)=2;sg(3)=3;

         到4的时候,情况就发生了变化。由于4不能直接转移到1,故后继集合只有{1,2,3},sg(4)=0;

   这里的状态用1,2,3,4之类是为了举例,而实际上状态不一定是这样转换,可能有很多种状态,不仅仅局限于单个数字,亦可以是某种局面,某个棋盘局面,某个路径局面,如果能找到”状态“只要这个游戏没有循环,在有限步数可以达到结果,就可以对这个游戏的每个状态求出sg值。

   求解

  求解顺序是这样的。首先找到所有的结尾局面。比如取石子游戏中的石子剩余为0,或是棋盘游戏中棋子的无路可走,所以这些状态的sg值都为0,然后从这个状态逆向求他的前驱,求出所有前驱的sg值。 如果了解过拓扑排序,那么很容易理解这个过程。用拓扑排序来讲就是一个状态的后继sg值没有确定时,这个状态的sg值也无法确定。故从所有无路可走的局面按照逆向的拓扑排序过程就可以求出所有可达状态的sg值。

   意义

  求出了sg值后,对于解这个游戏胜负关系有什么用呢?

  由上面的概念我们可以得到两个结论

  1.  sg值为0的状态,前一状态一定全是非0状态。(一定全是非0状态)
  2. sg值为非零的状态,一定能一步转移到0状态(不一定必须转到,是可以转到)

  由此我们可以进行决策。

  如果我们先手是非零,我们可以始终选择一步转移到sg值为0的状态,那么下一家一定只能转移到非0。那么到你又可以一步转移到0。循环如此决策,就可以让下一家最终转移到败态的0状态处,从而获得胜利

  如果我们先手是零,那么我们只能转移到非0状态,下一家可以用以上必胜决策进行操作,那么我们就必败。

  由此我们得到sg值与胜利与失败的关系。

  sg(此状态)=0时,先手的人必败。即(此状态)为必败态

  sg(此状态)≠0时,先手的人必胜。即(此状态)为必败态

  

  sg函数可以看做在这个游戏下规则的体现,可以直接反映出转移的方式。而sg()函数某个值可以视作某个状态下的胜负情况。

  往往一个游戏需要求的只是一个局面下的胜负情况。即一个sg(a),但实际运用中需要通过求出每个中间态的sg值来推出所需状态的sg值。是不是有点动态规划的思想?

三、nim博弈

  然而,一个游戏可能由多个状态共同构成,即两个状态间不能互相影响或转移到同一个末状态。这时单纯的sg函数就不够解题了。因此我们引入了一个多游戏间的决策,nim博弈。对于多个游戏间的博弈,不能用简单的sg函数表达。可以把这个复合的游戏进行转变,成为多个互不影响的游戏,即每个游戏可以各自求出各自的sg函数,解出各自状态对应的sg值。将多个游戏+状态的sg值综合起来的方式,即为nim。

  求解方式是res=sg[1]^sg[2]^sg[3]^...^sg[n]。即为这些游戏组合在一起后,整体的胜负态。

  右方sg值对应的是那个游戏的起始状态的sg值。

  (sg补充)sg值不用单纯的0和非0来表示的原因:

     多个游戏中,比如两个游戏,一个是必胜态,一个是必败态。如果按照单个游戏都要必胜的策略玩在一个游戏结束时,再玩下一个游戏,相当于先后手对调,先手必败。但是先手可以将某一个的状态从非零依旧转移到非零,从而改变整体胜负态,比如从sg=2转移到sg=1,对手无法再扭转回来,自己就可以获胜。但是如果sg更大,那么双方会持续做这个抉择。这个抉择是有尽头的,到这个尽头时的状态就决定了最后整体胜负态,这个决策可执行的次数就是sg值的数量,比如sg=5时,最多可以多转移4次(然而转移四次不是一定最佳选择,读者可以进行模拟思考) 因此sg值要取具体值,在异或的时候各自的信息也提供了用处。

  nim的决策公式推出的多个游戏组合后的值,就对应了整体的胜负态。

四、例题

  HDU-1524 A-chess-game

  两者综合运用多个棋子分别看待,对应两个独立的游戏,拓扑排序,然后求出每个局面打表求出对应的sg值。

  由于在同一套规则上,所以可以用同一个sg函数。将所有起始局面的sg值异或起来,根据是否为0求出结果

  代码如下

  

 #include<iostream>
#include<vector>
#include<cstring>
using namespace std; struct point{
int sg=;
vector <int> out;
vector <int> in;
int inn=;
int sgg=;
}graph[];
bool num[];
int mex(int x)
{
memset(num,,sizeof(num));
for(int i=;i<graph[x].in.size();i++){
num[graph[graph[x].in[i]].sg]=;
}
for(int i=;i<;i++){
if(num[i]==)return i;
}
}
void get_sg(int n)
{
for(int i=;i<n;i++){
for(int i=;i<n;i++){
if(graph[i].inn==&&graph[i].sgg==){
graph[i].sg=mex(i);
graph[i].sgg=;
for(int j=;j<graph[i].out.size();j++){
graph[graph[i].out[j]].inn--;
}
}
}
}
}
int main()
{
int n,m,a;
while(cin>>n){
for(int i=;i<=n;i++){
graph[i].out.clear();
graph[i].in.clear();
graph[i].sg=;
graph[i].inn=;
graph[i].sgg=;
}
for(int i=;i<n;i++){
cin>>m;
for(int j=;j<m;j++){
cin>>a;
graph[i].in.push_back(a);
graph[a].out.push_back(i);
graph[i].inn++;
}
}
get_sg(n);
int res=;
while(cin>>m){
if(m==)break;
res=;
for(int i=;i<m;i++){
cin>>a;
res^=graph[a].sg;
}
if(res!=)cout<<"WIN"<<endl;
else cout<<"LOSE"<<endl;
}
}
}

hdu 1524

  POJ-2960 S-Nim

  从0开始,对逆向根据规则对每个状态打表,求出sg值(如果某个点求过了,就不要重复求),再根据nim将多个结果异或起来

 #include<iostream>
#include<cstring>
using namespace std;
int sg[];
int s[];
bool num[];
int n,m;
int mex(int x){
memset(num,,sizeof(num));
for(int i=;i<n;i++){
if(x-s[i]>=){
num[sg[x-s[i]]]=;
}
}
for(int i=;i<;i++){
if(num[i]==)return i;
}
return -;
}
int get_sg(){
for(int i=;i<;i++){
sg[i]=mex(i);
}
}
int main()
{
int T ;
int p,q;
while (cin >> n) {
memset(sg,,sizeof(sg));
if (n == ) break;
for (int i = ; i < n; i ++)cin >> s[i];
get_sg();
cin >> m; while (m--){
int res = ;
cin>>q;
for(int i=;i<q;i++){
cin >> p;
res ^= sg[p];
}
if(res==)cout<<"L";
else cout<<"W";
}
cout<<endl;
}
return ;
}

poj 2960

  

如有疑问或异议,欢迎私信博主进行讨论与交流

  

博弈论基础之sg函数与nim的更多相关文章

  1. sg函数和nim游戏的关系

    sg函数和nim游戏的关系 本人萌新,文章如有错漏请多多指教-- 我在前面发了关于nim游戏的内容,也就是说给n堆个数不同的石子,每次在某个堆中取任意个数石子,不能取了就输了.问你先手是否必胜.然后只 ...

  2. 博弈论进阶之SG函数

    SG函数 个人理解:SG函数是人们在研究博弈论的道路上迈出的重要一步,它把许多杂乱无章的博弈游戏通过某种规则结合在了一起,使得一类普遍的博弈问题得到了解决. 从SG函数开始,我们不再是单纯的同过找规律 ...

  3. 博弈论初步(SG函数)

    讲解见此博客https://blog.csdn.net/strangedbly/article/details/51137432 理解Nim博弈,基于Nim博弈理解SG函数的含义和作用. 学习求解SG ...

  4. hdu 1847 博弈基础题 SG函数 或者规律2种方法

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

  5. 【博弈论】【SG函数】【线段树】Petrozavodsk Summer Training Camp 2016 Day 9: AtCoder Japanese Problems Selection, Thursday, September 1, 2016 Problem H. Cups and Beans

    一开始有n个杯子,每个杯子里有一些豆子,两个人轮流操作,每次只能将一个豆子移动到其所在杯子之前的某个杯子里,不过可以移动到的范围只有一段区间.问你是否先手必胜. 一个杯子里的豆子全都等价的,因为sg函 ...

  6. 【博弈论】【SG函数】【找规律】Gym - 101147A - The game of Osho

    以后这种题还是不能空想,必须打个表看看,规律还是比较好找的……具体是啥看代码.用SG函数暴力的部分就不放了. #include<cstdio> using namespace std; i ...

  7. 【博弈论】【SG函数】bzoj1777 [Usaco2010 Hol]rocks 石头木头

    仅有距根节点为奇数距离的节点的石子被移走对答案有贡献,∵即使偶数的石子被移走,迟早会被再移到奇数,而奇数被移走后,不一定能够在移到偶数(到根了). 最多移L个:石子数模(L+1),比较显然,也可以自己 ...

  8. 【博弈论】【SG函数】poj2311 Cutting Game

    由于异或运算满足结合律,我们把当前状态的SG函数定义为 它所能切割成的所有纸片对的两两异或和之外的最小非负整数. #include<cstdio> #include<set> ...

  9. 【博弈论】【SG函数】hdu1848 Fibonacci again and again

    某个状态的SG函数被定义为 除该状态能一步转移到的状态的SG值以外的最小非负整数. 有如下性质:从SG值为x的状态出发,可以转移到SG值为0,1,...,x-1的状态. 不论SG值增加与否,我们都可以 ...

随机推荐

  1. 《C++ Primer》读书笔记 第三章

    1.注意:头文件不应包含using声明.因为头文件的内容会拷贝到所有引用他的文件中去,对于某些程序来说,由于不经意间包含了一些名字,可能会产生名字冲突.2.string类型的读入:用cin读入stri ...

  2. shell多线程之进程间通信

    # 这是一个简单的并发程序,有如下要求: # .有两个程序a和b,希望他们能并发执行,以节约时间 # .a和b都是按照日期顺序执行,但b每日程序的前提条件是当日a的程序已经执行完毕 #解决方案: # ...

  3. 如何打造VUCA时代的敏捷型组织?

    王明兰 --原华为.微软创新与转型教练.华为云SaaS产品总监,著名精益&敏捷转型专家 VUCA最早来源于冷战时期,在现代世界意指商业世界越来越不确定性,越来越易变,越来越不可预测,我们已经进 ...

  4. Jenkins持续部署-自动生成版本号

    目录 Jenkins持续部署-自动生成版本号 目录 前言 目的 详细流程 获取SVN Reversion 获取需求号 设置编译前读取版本号 总结 参考文献 Jenkins持续部署-自动生成版本号 目录 ...

  5. MySQL 事务嵌套

    MySQL默认自动提交(autocommit=1),如果要开启事务,先关闭自动提交(autocommit=0): InnoDB支持事务,MyISAM不支持: MySQL不支持事务嵌套:已经开启事务后, ...

  6. linux 环境 安装jdk tomcat mysql git

    1.安装JDK 1.官方下载jdk,linux版本的rpm包 2.安装rz sz ----------编译安装 //安装 cd /tmp wget http://www.ohse.de/uwe/rel ...

  7. spring 5.x 系列第7篇 —— 整合Redis客户端 Jedis和Redisson (xml配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件 2.2 单机配置 ...

  8. python logging模块使用总结

    目录 logging模块 日志级别 logging.basicConfig()函数中的具体参数含义 format参数用到的格式化信息 使用logging打印日志到标准输出 使用logging.base ...

  9. eclipse中一个项目引用另一个项目,运行报:java.lang.NoClassDefFoundError

    项目右击-properties-Java Build Path -Porjects-add.选中了某个项目. 项目用tomcat启动时,报错:java.lang.NoClassDefFoundErro ...

  10. 关于Keepalive的那些事

    服务端很多同学包括自己对keepalive理解不清晰,经常搞不清楚,TCP也有keepalive,HTTP也有keepalive,高可用也叫keepalive,经常混淆这几个概念.做下这几个概念的简述 ...