码迷,mamicode.com
首页 > 其他好文 > 详细

SG函数入门

时间:2019-03-03 19:02:22      阅读:192      评论:0      收藏:0      [点我收藏+]

标签:john   描述   poj 1704   fine   cpp   inline   题目   这一   个人   

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\n");
        else printf("Nacci\n");
    }
    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函数入门

标签:john   描述   poj 1704   fine   cpp   inline   题目   这一   个人   

原文地址:https://www.cnblogs.com/My-snowing/p/10466769.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!