在算法竞赛中,博弈论题目往往是以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. qt 维护x86和arm两套编译环境

    1.中间库: 中间库都放在middlewares目录,include头文件相同,所以不需要特殊处理,只要特殊处理lib安装目录, 示例pro文件如下: TEMPLATE = lib TARGET = ...

  2. windows Service 之调试过程(附加到进程里调试,而且启动时间不能超过30秒)

    最近第一次用C#写了一个windows service ,其实实现的内容比较简单.就是启动remoting 连接,但是调试相对初次写windws service 的我来说,比较烦.没有经验,而且没办法 ...

  3. Python print不换行输出的替代方法

    Python的不换行输出好蛋疼,查了半天书没查到... python中print默认是换行的.想让它不换行,网上说可以在print后面加上逗号.如:print 'aaa',这个方法行的通,但是中间多了 ...

  4. Spring之基于注解的注入

    对于DI使用注解,将不再需要在Spring配置文件中声明Bean实例.Spring中使用注解,需要在原有Spring运行环境基础上再做一些改变,完成以下三个步骤. (1)导入AOP的Jar包.因为注解 ...

  5. JavaWEB路径总结

    这篇文章是小编一直想写的一篇,主要是对web阶段中各个路径进行的一些总结,希望读者看过之后对于路径方面有一个清晰的认识.首先声明一点:世界上一切东西都是相对的,对于这点而言,相信大家并不陌生,从初中开 ...

  6. 浏览器中查看HTTP的头部文件

    本文以chrome浏览器为例,来讲解下在浏览器中,如何查看http的头部文件. 1.打开chrome浏览器,输入地址,如下图所示. 2.鼠标右击,在右键菜单中选择[检查],如下图所示. 3.选择“Ne ...

  7. Java NIO学习系列二:Channel

    上文总结了Java NIO中的Buffer相关知识点,本文中我们来总结一下它的好兄弟:Channel.上文有说到,Java NIO中的Buffer一般和Channel配对使用,NIO中的所有IO都起始 ...

  8. 第一章 corejava的入门

    第一章 corejava的入门一:什么是语言语言=os+数据结构+算法+思想os:操作系统数据结构:队,栈,二叉树,链表算法:做游戏开发时非常重要面试题:int a>0,b>0只使用一条输 ...

  9. Google Chrome浏览器插件入门开发

    --1. 在html文件中引用js 文件 --2.在Google Chrome中开发简单插件 1.首先,简单说明一下在html 中引用js 文件: 将kittenbook.html 和 kittenb ...

  10. python小方法 随笔记

    1. 元组和列表的接收 s1,s2 = [,] print(s1,s2) # 执行结果: 1 2 s3,s4 = (,) print(s3,s4)# 执行结果: 3 4 2. 变量值的交换 a = b ...