简介:
sg函数和sg定理是公平组合游戏中的重要组成部分,这篇文章是结合博客(末尾会贴博客链接)和我之前写博弈论题目的总结与反思,因为之前并没有系统的学习sg函数,所以做博弈论的题目可谓是一波三折,可以说学习sg函数的相关内容加深了我对博弈论的理解,让我对博弈论题目有了一个系统的思考。这篇博客是一篇小小的总结,以后提高最重要的方法还是需要多刷题。
关于概念
必胜点和必败点
这个在我之前推导博弈论的题目时就有了比较深刻的概念和印象,对于必胜点的推导与总结是博弈论的必经之路。
P点:必败点,换而言之,就是谁处于此位置,则在双方操作正确的情况下必败。
N点:必胜点,处于此情况下,双方操作均正确的情况下必胜。
必胜点和必败点的性质
1、所有终结点是 必败点 P 。(我们以此为基本前提进行推理,换句话说,我们以此为假设)
2、从任何必胜点N 操作,至少有一种方式可以进入必败点 P。
3、无论如何操作,必败点P 都只能进入 必胜点 N。
组合游戏
在竞赛中,组合游戏的题目一般有以下特点
题目描述一般为
- A,B 2人做游戏
- A B交替进行某种游戏规定的操作,每操作一次,选手可以在有限的操作(操作必须合法)集合中任选一种。
- 对于游戏的任何一种可能的局面,合法的操作集合只取决于这个局面本身,不取决于其它因素(跟选手,以前的所有操作无关)
- 如果当前选手无法进行合法的操作,则为负
SG函数的分析与推导
取石子问题
我们以取石子问题为例
有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?
这题非常直白的问你sg的值是多少?但是我们如果抛开这个sg不看,仔细观察这个题面,你可以总结出一些很明显的特征
1.2人的公平游戏
2.交替操作
3.操作方法一样
4.无法操作的时候为负(这点其实非常重要,在后面的题目中有涉及到)
我们发现这其实就是一个模型,就像二分图,dp那些题目都有一个固定的模型,我们解决这道题目就必须找出模型里面的要素,上面的四个要素便是博弈论或者说是公平组合游戏中最重要的四个要素。
到这里我们就需要了解SG函数的概念了
Sprague-Grundy定理(SG定理):
游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。而Bouton定理就是Sprague-Grundy定理在Nim游戏中的直接应用,因为单堆的Nim游戏 SG函数满足 SG(x) = x。
SG函数
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的最小非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(da),SG(db),SG(dc),那么SG(x) = mex{SG(da),SG(db),SG(dc)}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时
下面这里就是介绍这道题目的思路(贴得别人写的思路):
博客链接:链接
SG[0]=0,f[]={1,3,4},
x=1 时,可以取走1 - f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] }= mex{0} = 1;
x=2 时,可以取走2 - f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] }= mex{1} = 0;
x=3 时,可以取走3 - f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} =1;
x=4 时,可以取走4- f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2;
x=5 时,可以取走5 - f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3;
以此类推…
x 0 1 2 3 4 5 6 7 8…
SG[x] 0 1 0 1 2 3 2 0 1…
由上述实例我们就可以得到SG函数值求解步骤,那么计算1~n的SG函数值步骤如下:
1、使用 数组f 将 可改变当前状态 的方式记录下来。
2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。
3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)。
4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值。
代码:
//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[N],SG[MAXN],S[MAXN];
void getSG(int n){
int i,j;
memset(SG,0,sizeof(SG));
//因为SG[0]始终等于0,所以i从1开始
for(i = 1; i <= n; i++){
//每一次都要将上一状态 的 后继集合 重置
memset(S,0,sizeof(S));
for(j = 0; f[j] <= i && j <= N; j++)
S[SG[i-f[j]]] = 1; //将后继状态的SG函数值进行标记
for(j = 0;; j++) if(!S[j]){ //查询当前后继状态SG值中最小的非零值
SG[i] = j;
break;
}
}
}
小习题
接下来就可以做一个小练习:
杭电小习题
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1000;
typedef long long ll;
int n ,m , p;
ll f[N+10];
ll s[N+10];
ll sg[N+10];
int main(){
f[1] = 1;
f[0] = 1;
for(int i = 2;i <= N ;i ++ ){
f[i] = f[i-1] + f[i-2];
}
memset(sg , -1, sizeof sg);
sg[0] = 0;
for(int i = 1;i <= N ; i ++){
memset(s,0,sizeof s);
for(int j = 0 ; f[j] <= i && j <= N ;j ++){
s[sg[i-f[j]]] = 1;
}
for(int j = 0 ; ; j ++){
if(!s[j]){
sg[i] = j;
break;
}
}
}
while(~scanf("%d %d %d" ,&n ,&m ,&p)){
if(n + m + p == 0)break;
if(sg[n] ^ sg[m] ^ sg[p] ){
puts("Fibo");
}
else puts("Nacci");
}
}