博弈论初步——SG函数
博弈论初步
博弈\(——\)从今天开始,\(Alice\)和\(Bob\)有了新的游戏
一、博弈图
如果我们把每一个博弈状态看成一个点,如果每一个状态可以移动到另外一个状态则连接一条边,这样我们就构建了一张有向图,不难发现这是一张有向无环图。
相对于之前抽象的博弈模型,我们现在得到了一个具象的图模型。或许在这张图上我们可以获得更多的信息。
必败与必胜
在熟悉上一篇我写过的四种基本模型之后,就会知道所谓必胜状态就是指先手经过行动之后,将必败状态留给了对手,而必败状态则是指无论先手怎么移动,后手仍然会有一个必胜状态。
也就是:
必胜态:可以一步走到必败态
必败态:走不到必败态
回顾\(Nim\)博弈的证明过程,在那个时候我们似乎就引出了三个定理:
定理 1: 没有后继状态的状态是必败状态。
定理 2: 一个状态是必胜状态当且仅当存在至少一个必败状态为它的后继状态。
定理 3: 一个状态是必败状态当且仅当它的所有后继状态均为必胜状态。
通过这三个定理,我们就可以拓扑排序的方式得出每一个状态点是必胜点还是必败点的。
有向图的核
最后引入有向图核的概念。
给定\(DAG<V,E>\),如果有点集\(S\)满足:
- \(S\)是独立集
- \(S\)的补集\(S'\)中的任意点可以一步到达\(S\)
则\(S\)为图的一个核。
对比一下我们之前引出的三定理,不难发现,核的定义与必败态不谋而合,必败态无法到达必败态,而核内部的点也是无法连通的。相对的,由于核外的点都可以到达核这和必胜态的定义,即可以移动到必败态也是一样的。
得出一个结论:
核内节点对应 \(SG\) 组合游戏的必败态
二、\(SG\)函数与\(SG\)定理
找到博弈图之后我们可以在这张图上研究更多的东西。定义一个\(DAG\),一个入度为\(0\)的点为起点,起点有一个棋子,两名棋手轮流移动棋子,无法移动者失败。在这张图上面,我们将会得到\(SG\)。
1、\(mex\)运算
一种集合的运算:
表示不属于该集合的最小非负整数。
\(eg:mex\{0,1,3,5,7\} = 2\)。
2、\(SG\)函数定义
\(SG(x) = mex\{SG(y)|y\)是\(x\)的后继节点\(\}\)
- 所有出度为\(0\)的点\(x\),\(SG(x) = 0\)。
- 若\(SG(x)=0\),该节点为必败点,不为\(0\)则为必胜点。
这就意味着:
(1)如果一个状态的\(SG\)值为\(0\),那么它的任何一个后继状态的\(SG\)值不为\(0\)。
(2)如果一个状态\(SG\)值不为\(0\),那么它一定有一个后继状态\(SG\)值为\(0\)。
3、\(SG\)定理
对于\(n\)个有向图游戏组成的组合游戏,设起点为\(s_1,s_2,...,s_n\)。
有\(SG\)定理:当且仅当\(SG(s_1)\oplus SG(s_2)\oplus ...\oplus SG(s_n) \neq 0\)时,这个游戏是先手必胜的。同时这是一个组合游戏的游戏状态\(x\)的\(SG\)值。
4、再看\(Nim\)游戏
\(SG\)定理很难不让人联想到\(Nim\)游戏。所以我们用另外一种视角来看看\(Nim\)游戏。
我们将一个\(Nim\)游戏转换成一个有向图游戏。
我们将一个有\(x\)个物品的石子堆是为结点\(x\),当且仅当石子数\(y\)小于\(x\)时,\(x\)可以到达\(y\),由于可以取任意多个石子,所以\(y\)可以从\(0\)到\(x-1\),易得\(SG(x) = x\)。
我们将一个堆看作一个有向图游戏,一个\(Nim\)就是\(n\)个有向图游戏。
根据上面的推论,可以得到\(SG(a_i) = a_i\),再根据\(SG\)定理,就得到了\(Nim\)和的结论。
三、来看几个题
一道模板题
大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的ACM都没工夫练习了,反正我知道的Kiki和Cici都是如此。当然,作为在考场浸润了十几载的当代大学生,Kiki和Cici更懂得考前的放松,所谓“张弛有道”就是这个意思。这不,Kiki和Cici在每天晚上休息之前都要玩一会儿扑克牌以放松神经。
“升级”?“双扣”?“红五”?还是“斗地主”?
当然都不是!那多俗啊~
作为计算机学院的学生,Kiki和Cici打牌的时候可没忘记专业,她们打牌的规则是这样的:
1、 总共n张牌;
2、 双方轮流抓牌;
3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)
4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者;
假设Kiki和Cici都是足够聪明(其实不用假设,哪有不聪明的学生~),并且每次都是Kiki先抓牌,请问谁能赢呢?
当然,打牌无论谁赢都问题不大,重要的是马上到来的CET-4能有好的状态。
Good luck in CET-4 everybody!
0 ≤ 𝑛 ≤ 1000
题解:直接跑\(SG\)函数即可,跑完之后打表发现规律,即3的倍数都是必败点。
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#define ll long long
const ll mod = 1e9+7;
const ll N = 3050;
vector<ll> p2;
bool vis[N];
ll sg[N];
void init(){
p2.push_back(1);
for(ll i = 1;;i++){
p2.push_back(p2[i-1]*2);
if(p2[i] > 1000)break;
}
for(ll i = 1;i <= 1010;i++){
memset(vis,false,sizeof vis);
for(ll j = 0;j < p2.size() && p2[j] <= i;j++){
vis[sg[i-p2[j]]] = true;
}
for(ll j = 0;;j++){
if(!vis[j]){
sg[i] = j;
break;
}
}
}
}
int main() {
ios::sync_with_stdio(false);
init();
ll n = 0;
while(cin>>n){
//if(sg[n])cout<<n<<' ';
if(sg[n])puts("Kiki");
else puts("Cici");
}
return 0;
}
另外一道模板题:
今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
这是一个二人游戏,两人轮流走。一共有3堆石子,数量分别是𝑚, 𝑛, 𝑝个◦每走一步可以选择任意一堆石子,然后取走
𝑓个。𝑓只能是菲波那契数列中的元素。
最先取光所有石子的人为胜者。
假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。
0 ≤ 𝑛, 𝑚, 𝑝 ≤ 1000
题解:
\(SG\)定理和\(SG\)函数的简单应用,可以看成\(n,m,p\)三堆的有向图游戏,求出对应的\(SG\)值之后,在使用\(SG\)定理异或即可。
code:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#define ll long long
const ll mod = 1e9+7;
const ll N = 3050;
vector<ll> fib;
bool vis[N];
ll sg[N];
void init(){
fib.push_back(1),fib.push_back(1);
for(ll i = 2;;i++){
fib.push_back(fib[i-1]+fib[i-2]);
if(fib[i] > 1000) break;
}
for(ll i = 1;i <= 1010;i++){
memset(vis,false,sizeof vis);
for(ll j = 0;j < fib.size() && fib[j] <= i;j++){
vis[sg[i-fib[j]]] = true;
}
for(ll j = 0;;j++){
if(!vis[j]){
sg[i] = j;
break;
}
}
//cout<<i<<' '<<sg[i]<<endl;
}
}
int main() {
ios::sync_with_stdio(false);
init();
ll n,m,p;
while(cin>>n>>m>>p){
if(n == 0 && m == 0 && p == 0)break;
if(sg[n]^sg[m]^sg[p])puts("Fibo");
else puts("Nacci");
}
return 0;
}