zoukankan      html  css  js  c++  java
  • 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和等于各个游戏的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

  • 相关阅读:
    [ASP.NET]ScriptManager控件使用
    [ASP.NET]使用uploadify上传图片,并在uploadify按钮上生成预览图
    [Javascript]js判断是否为undefined类型
    [Bug]转:使用jquery的 uploadify,在谷歌浏览器上总会崩溃的解决方法
    [NHibernate]Nhibernate如何映射sqlserver中image字段
    远程访问CENTOS的MYSQL数据库设置
    DELPHI的一些开源项目GIT地址
    unigui导出EXCEL使用NATIVEEXCEL
    IdHTTPServer(indy10)开发REST中间件
    centos7安装MYSQL
  • 原文地址:https://www.cnblogs.com/My-snowing/p/10466769.html
Copyright © 2011-2022 走看看