SG函数入门

Posted my-snowing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SG函数入门相关的知识,希望对你有一定的参考价值。

SG函数入门

必胜点与必败点

概念

P点:必败点,换句话说,就是在双方都选择最优策略的情况下,谁处于此状态谁必败。

N点:必胜点,换句话说,就是在双方都选择最优策略的情况下,谁处于此状态谁必胜。

性质

1.所有的终结点都是必败点P。

2.从任意的必胜点N进行操作,至少有一种方式到达一个必败点。

3.从任意的一个必败点P进行操作,只可能到达必胜点N。


我们研究必胜点与必败点的目的是以此为题来简化博弈的情况,有助于我们分析策略。通常我们分析必胜点和必败点都是以终结点为起始点进行逆序分析。

我们以一道题为例hdu 1847 Good Luck in CET-4 Everybody!

例题hdu 1847 Good Luck in CET-4 Everybody!

题目描述

大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的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能有好的状态。

题目大意

现在有n张牌两个人取每次每个人只能取(2^k)张牌问是否先手必胜(肯定有万恶的多组数据)

当 n = 0 时,显然为必败点,因为此时你已经无法进行操作了

当 n = 1 时,因为你一次就可以拿完所有牌,故此时为必胜点

当 n = 2 时,也是一次就可以拿完,故此时为必胜点

当 n = 3 时,要么就是剩一张要么剩两张,无论怎么取对方都将面对必胜点,故这一点为必败点。

以此类推,最后你就可以得到;

? n : 0 1 2 3 4 5 6 ...

position: P N N P N N P ...

然后我们发现这道题的 P/N点的分布是有规律的,所以我们就可以O(T)的求解这道题了。


然后呢我们来考虑更加复杂的一个问题hdu 2147 kiki‘s game

例题hdu 2147 kiki‘s game

题意

先说题意,有两个人在玩一个方格游戏,起初是一个n×m的方格,刚开始在(1,m)坐标处有一个石子,每一次每个人可以把石子移向向,向,向左下,等到当前的人无法进行移动时,这个人失败,每次都是先手先走,问最终的答案。

思路

这是一道非常裸的NP状态转移的博弈问题。

这类题我们最好画一个表格,然后发挥自己的想象去找规律。

由于我们每次只能向左下角走,所以显然左下角为必败点。

那么所有可以到达这个点的点都为必胜点,然后我们一步步往下推最后一定可以得到一个类似这样的表格。

P N P N
N N N N
P N P N
N N N N
P N P N

我们可以发现规律只有当行列都为奇数时才为必败点,其他情况均为必胜点

所以这道题当行列都为奇数时Kiki无法赢得比赛。

SG 函数

首先我们需要先了解一个运算mex,这是一个对于集合的运算,mex(S)表示集合S中最小没有出现过的自然数。例如:mex{0,1,2,4}=3,mex{2,3,5}=0,mex{}=0

对于任意状态x,我们定义SG(X)=mex(S),其中S表示x的后继状态的SG函数值的集合。举个栗子:x有三个后继状态分别为SG(a),SG(b),SG(c);那么SG(x)=mex{SG(a),SG(b),SG(c)}。这样的集合S的最终状态必然是空集,所以当某一点的SG(x)=0,当且仅当x为为必败点时成立。

Sprague-Grundy定理(SG定理)

游戏和的等于各个游戏的SG函数的Nim和。这样就可以把一个游戏分而治之,从而简化问题。而Bouton定理就是SG定理在Nin游戏中的直接应用,因为单堆的Nim游戏SG函数满足SG(x)=x.

举个栗子

取石子

题面

有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?

思路

先找终结点当个数为0时。那么有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
SG[X] 0 1 0 1 2 3 2 0

由上述实例我们就可以得到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;
        }
    }
}

Fibonacci again and again

题面

任何一个大学生对菲波那契数列(Fibonacci numbers)应该都不会陌生,它是这样定义的:
F(1)=1;
F(2)=2;
F(n)=F(n-1)+F(n-2)(n>=3);
所以,1,2,3,5,8,13……就是菲波那契数列。
在HDOJ上有不少相关的题目,比如1005 Fibonacci again就是曾经的浙江省赛题。
今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
1、 这是一个二人游戏;
2、 一共有3堆石子,数量分别是m, n, p个;
3、 两人轮流走;
4、 每走一步可以选择任意一堆石子,然后取走f个;
5、 f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);
6、 最先取光所有石子的人为胜者;

假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。

数据范围为1000

#include <stdio.h>
#include <string.h>
#define MAXN 1000 + 10
#define N 20
int f[N],SG[MAXN],S[MAXN];
void getSG(int n){
    int i,j;
    memset(SG,0,sizeof(SG));
    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;
        for(j = 0;;j++) if(!S[j]){
            SG[i] = j;
            break;
        }
    }
}
int main(){
    int n,m,k;
    f[0] = f[1] = 1;
    for(int i = 2; i <= 16; i++)
        f[i] = f[i-1] + f[i-2];
    getSG(1000);
    while(scanf("%d%d%d",&m,&n,&k),m||n||k){
        if(SG[n]^SG[m]^SG[k]) printf("Fibo
");
        else printf("Nacci
");
    }
    return 0;
}

其他题目

POJ 2234 Matches Game
HOJ 4388 Stone Game II

POJ 2975 Nim
HOJ 1367 A Stone Game
POJ 2505 A multiplication game
ZJU 3057 beans game
POJ 1067 取石子游戏
POJ 2484 A Funny Game
POJ 2425 A Chess Game
POJ 2960 S-Nim
POJ 1704 Georgia and Bob
POJ 1740 A New Stone Game
POJ 2068 Nim
POJ 3480 John
POJ 2348 Euclid‘s Game
HOJ 2645 WNim
POJ 3710 Christmas Game
POJ 3533 Light Switching Game

以上是关于SG函数入门的主要内容,如果未能解决你的问题,请参考以下文章

SG函数入门

hdu1848(sg函数打表)

博弈论 SG函数Fibonacci again and again

shuoj 418 丢史蒂芬妮(素数筛+sg函数)

博弈论经典入门

HDOJ1848NIM博弈 SG函数