博弈论基础之sg函数与nim
在算法竞赛中,博弈论题目往往是以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值后,对于解这个游戏胜负关系有什么用呢?
由上面的概念我们可以得到两个结论
- sg值为0的状态,前一状态一定全是非0状态。(一定全是非0状态)
- 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的更多相关文章
- sg函数和nim游戏的关系
sg函数和nim游戏的关系 本人萌新,文章如有错漏请多多指教-- 我在前面发了关于nim游戏的内容,也就是说给n堆个数不同的石子,每次在某个堆中取任意个数石子,不能取了就输了.问你先手是否必胜.然后只 ...
- 博弈论进阶之SG函数
SG函数 个人理解:SG函数是人们在研究博弈论的道路上迈出的重要一步,它把许多杂乱无章的博弈游戏通过某种规则结合在了一起,使得一类普遍的博弈问题得到了解决. 从SG函数开始,我们不再是单纯的同过找规律 ...
- 博弈论初步(SG函数)
讲解见此博客https://blog.csdn.net/strangedbly/article/details/51137432 理解Nim博弈,基于Nim博弈理解SG函数的含义和作用. 学习求解SG ...
- hdu 1847 博弈基础题 SG函数 或者规律2种方法
Good Luck in CET-4 Everybody! Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K ...
- 【博弈论】【SG函数】【线段树】Petrozavodsk Summer Training Camp 2016 Day 9: AtCoder Japanese Problems Selection, Thursday, September 1, 2016 Problem H. Cups and Beans
一开始有n个杯子,每个杯子里有一些豆子,两个人轮流操作,每次只能将一个豆子移动到其所在杯子之前的某个杯子里,不过可以移动到的范围只有一段区间.问你是否先手必胜. 一个杯子里的豆子全都等价的,因为sg函 ...
- 【博弈论】【SG函数】【找规律】Gym - 101147A - The game of Osho
以后这种题还是不能空想,必须打个表看看,规律还是比较好找的……具体是啥看代码.用SG函数暴力的部分就不放了. #include<cstdio> using namespace std; i ...
- 【博弈论】【SG函数】bzoj1777 [Usaco2010 Hol]rocks 石头木头
仅有距根节点为奇数距离的节点的石子被移走对答案有贡献,∵即使偶数的石子被移走,迟早会被再移到奇数,而奇数被移走后,不一定能够在移到偶数(到根了). 最多移L个:石子数模(L+1),比较显然,也可以自己 ...
- 【博弈论】【SG函数】poj2311 Cutting Game
由于异或运算满足结合律,我们把当前状态的SG函数定义为 它所能切割成的所有纸片对的两两异或和之外的最小非负整数. #include<cstdio> #include<set> ...
- 【博弈论】【SG函数】hdu1848 Fibonacci again and again
某个状态的SG函数被定义为 除该状态能一步转移到的状态的SG值以外的最小非负整数. 有如下性质:从SG值为x的状态出发,可以转移到SG值为0,1,...,x-1的状态. 不论SG值增加与否,我们都可以 ...
随机推荐
- C++Builder 中如何修改服务描述,使用ChangeServiceConfig2(SERVICE_CONFIG_DESCRIPTION)
http://blog.csdn.net/jpexe/article/details/4296955 // ---------------------------------------------- ...
- Linux历史,安装,分区,版本
Linux 历史 1970年是 UNIX元年,这一年 Kenneth Lane Thompson 和 Dennis Ritchie 合作编写了UNIX系统. Stallman 发起了GNU 计划,他本 ...
- Python一基本数据类型(dict)
一. 字典的简单介绍 字典(dict)是python中唯一的一个映射类型.他是以{ }括起来的键值对组成. 在dict中key是 唯一的. 在保存的时候, 根据key来计算出一个内存地址. 然后 ...
- 一文详解 LVS、Nginx 及 HAProxy 工作原理( 附大图 )
当前大多数的互联网系统都使用了服务器集群技术,集群是将相同服务部署在多台服务器上构成一个集群整体对外提供服务,这些集群可以是 Web 应用服务器集群,也可以是数据库服务器集群,还可以是分布式缓存服务器 ...
- Swift的访问控制讲解
Swift中访问修饰符总共有5种,分别为fileprivate,private,internal,public和open,其中,fileprivate以及open是Swift 3新添加的.因为过去的S ...
- Centos7 fstab盘符挂载硬盘导致重启系统失败解决办法
服务器拥有多个硬盘插槽,在进行维护或重启时,这些硬盘的相对位置可能发生变化.利用盘符(dev/vda)方式挂载磁盘,可能由于磁盘顺序变化导致重启时读取fstab文件发生错误,从而无法正常重启服务器. ...
- 使用Core Audio实现VoIP通用音频模块
最近一直在做iOS音频技术相关的项目,由于单项直播SDK,互动直播SDK(iOS/Mac),短视频SDK,都会用到音频技术,因此在这里收集三个SDK的音频技术需求,开发一个通用的音频模块用于三个SDK ...
- docker部署asp.net core
上一篇文章我们成功的在win10上边安装了docker,这篇文章,我们将在docker中部署asp.net core程序, 先来一张运行成功的hello world镇楼 现在开始,首先创建一个asp. ...
- centos安装netcat TCP UDP测试工具 简称 nc,安全界叫它瑞士军刀
centos安装netcat 今天安装swoole后,测试UDP服务需要用到netcat,然而百度了很多安装方法,并没有一个好用的.几经尝试,终于安装成功,现在就分享给大家,以供参考. 配置环境:ce ...
- 机器学习经典算法之Apriori
一. 搞懂关联规则中的几个概念 关联规则这个概念,最早是由 Agrawal 等人在 1993 年提出的.在 1994 年 Agrawal 等人又提出了基于关联规则的 Apriori 算法,至今 Apr ...