博弈论基础之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值增加与否,我们都可以 ...
随机推荐
- Qt for android触摸手势事件QGestureEvent
在触摸设备上可以使用Qt的手势事件 要激活手势事件,需要执行以下操作: 第一步,为QWidget控件注册手势事件 QList<Qt::GestureType> gestures; gest ...
- DirectX的替代品 SDL 简介
DirectX的替代品 SDL 简介 什么是SDL? 即 Simple DirectMedia Layer,使用 LGPL 许可证. 免费的跨平台多媒体应用编程接口 用于游戏.游戏开发工具.模拟器.样 ...
- windows 下 php 实现在线预览附件(pdf)
(写的有点啰嗦,具体的实现方法只是粗体和代码就够了) 给市场部门用dede做个cms,需要附件在线查看.公司有个系统(就叫develop_cms吧)是已经实现的,本以为很容易,不过是下一个插件然后把附 ...
- Adobe cs6 全系列软件绿色破解版-一键安装
下载地址: 链接:https://pan.baidu.com/s/1THssmSS-SnyNc2DW7Wr8cA 提取码:y3tq 软件介绍 作为全球领先的多媒体设计软件供应商,Adobe Syste ...
- Python字典的合并与拆分
1.字典的合并 dict1={1:[1,11,111],2:[2,22,222]} dict2={3:[3,33,333],4:[4,44,444]} dictMerged2=dict(dict1, ...
- 高中生也能读懂的Docker入门教程
Docker 是 Golang 编写的, 自 2013 年推出以来,受到越来越多的开发者的关注.如果你关注最新的技术发展,那么你一定听说过 Docker.不管是云服务还是微服务(Microservic ...
- 09 audio和vedio标签
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8& ...
- 中转Webshell 绕过安全狗(一)
前言 听说中国菜刀里有后门.抓包我是没有监测到异常数据包.为了以防万一,且更好使用中国菜刀硬杠安全狗.笔者收集了一下资料.无耻的copy大佬的源码,只是在大佬的基础上简单修改了一下,达到Webshel ...
- 你需要知道的c# Timer 的垃圾回收机制。
通常我们需要定时执行一段任务的时候,我们就需要定时器,这时我们就可以使用c# System.Threading空间中的 Timer定时器;他是个异步定时器,时间到时每次都是在线程池中分配一个线程去执行 ...
- Programming In Lua 第五章
1, 2, 3, 4, 5, 6, 7, 8, 9, 第9点非常重点. 10,