在算法竞赛中,博弈论题目往往是以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. ThoughtWorks 面试备忘录

    ThoughtWorks 面试备忘录 前言 前段时间 ThoughtWorks 在网上和拉勾网合作搞了一次网络招聘,名为抛弃简历!让代码说话!,可谓赚足了眼球,很多程序猿纷纷摩拳擦掌.踊跃提交代码,在 ...

  2. Linux编辑器Vim和Emacs入门

    sudo 命令 debian系统没有自带,需要安装: apt-get install sudo 安装位置为 /usr/bin/sudo,对应配置文件为 /etc/sudoers sudoers授权格式 ...

  3. SpringBoot2.1.6 整合CXF 实现Webservice

    SpringBoot2.1.6 整合CXF 实现Webservice 前言 最近LZ产品需要对接公司内部通讯工具,采用的是Webservice接口.产品框架用的SpringBoot2.1.6,于是采用 ...

  4. 08 Javascript的函数

    函数:就是将一些语句进行封装,然后通过调用的形式,执行这些语句. 函数的作用: 将大量重复的语句写在函数里,以后需要这些语句的时候,可以直接调用函数,避免重复劳动. 简化编程,让编程模块化. cons ...

  5. ElasticSearch学习(一):ElasticSearch介绍

    一.ElasticSearch是什么? ElasticSearch是一款非常强大的.基于Lucene的开源搜索及分析引擎,可以帮助你从海量数据中,快速找到相关的数据信息. 比如,当你在GitHub上搜 ...

  6. 【死磕线程】线程同步机制_java多线程之线程锁

    1.线程各种状态间的切换,用图表示的话简单清晰: 图出处:https://www.cnblogs.com/bhlsheji/p/5099362.html(博主对每个状态解释的清晰明了) 2.为什么需要 ...

  7. 如何在VMware12中安装centos6.7系统

    一.安装虚拟机,步骤如下: 1.安装好VMware12软件(略过),安装完后点击创建新的虚拟机 2.选择自定义类型安装 3.点击下一步 4.选择稍后安装操作系统,点击[下一步]. 5.客户机操作系统选 ...

  8. KVM虚拟机迁移至VMware

    1.将kvm下虚拟机关机: [root@localhost ~]# virsh list --all Id Name State ----------------------------------- ...

  9. 走近Java之幕后的String

    前几天,有个同事问了我一个表面看起来显而易见的问题,是关于String的,我们一起来看一下(如果有说的不正确的地方,欢迎大家指正). java中,字面量在编译期计算,并且String字面量作为常量,存 ...

  10. ASP.NET Core系列(一): .NET Core简介及安装开发环境

    大家都知道Java是跨平台的,.NET因为不具有跨平台的特性,被越来越多的开发者诟病,之前有各种间接的跨平台的方案,比如mono.但是由于各种兼容问题,最终 .NET Core出现了,它可以让程序在W ...