zoukankan      html  css  js  c++  java
  • SG函数入门&&HDU 1848

    SG函数

    sg[i]为0表示i节点先手必败。

    首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。

    对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Grundy函数g如下:g(x)=mex{ g(y) | y是x的后继 },这里的g(x)即sg[x]

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

    sg[0]=0,f[]={1,3,4},

    x=1时,可以取走{1}个石子,剩余{0}个,mex{sg[0]}=mex{0},故sg[1]=1;

    x=2时,可以取走{1}个石子,剩余{1}个,mex{sg[1]}=mex{1},故sg[2]=0;

    x=3时,可以取走{1,3}个石子,剩余{2,0}个,mex{sg[2],sg[0]}=mex{0,0},故sg[3]=1;

    x=4时,可以取走{1,3,4}个石子,剩余{3,1,0}个,mex{sg[3],sg[1],sg[0]}=mex{1,1,0},故sg[4]=2;

    x=5时,可以取走{1,3,4}个石子,剩余{4,2,1}个,mex{sg[4],sg[2],sg[1]}=mex{2,0,1},故sg[5]=3;

    以此类推.....

       x         0  1  2  3  4  5  6  7  8....

    sg[x]      0  1  0  1  2  3  2  0  1....

     

    计算从1-n范围内的SG值。

    f(存储可以走的步数,f[0]表示可以有多少种走法)

    f[]需要从小到大排序

    1. 可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1);
    2. 可选步数为任意步,SG(x) = x;
    3. 可选步数为一系列不连续的数,用GetSG()计算

     证明略(不会

    求SG值

    1. 打表

    //f[]: 可以取走的石子数量
    //sg[]: 1~n的sg函数值
    //vis[]: mex{}
    void getSG(int n) {
        memset(sg, 0, sizeof(sg));
        for (int i = 1; i <= n; i++) {
            memset(vis, 0, sizeof(vis));
            for (int j = 0; f[j] <= i && j < maxm; j++)
                vis[sg[i - f[j]]] = 1;
            for (int j = 0;; j++) if (!vis[j]) {   //最小的未出现的正整数
                sg[i] = j;
                break;
            }
        }
    }

    2. 记忆化搜索

    //记忆化搜索
    //f[]: 从小到大排序
    //sg[]: 初始化为-1
    //maxm,石子个数,集合的最大数量
    int dp(int x)
    {
        if (sg[x] != -1)  return sg[x];
        bool vis[maxn];
        memset(vis, 0, sizeof(vis));
        for (int i = 0; i < maxm; i++)
        {
            if (f[i] <= x)
            {
                dp(x - f[i]);
                vis[sg[x - f[i]]] = 1;
            }
        }
        for (int i = 0;; i++)
        {
            if (!vis[i])  return sg[x] = i;
        }
    }

    HDU 1848

      今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
    1、  这是一个二人游戏;
    2、  一共有3堆石子,数量分别是m, n, p个;
    3、  两人轮流走;
    4、  每走一步可以选择任意一堆石子,然后取走f个;
    5、  f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);
    6、  最先取光所有石子的人为胜者;

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

    代码:

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<cmath>
     4 #include<algorithm>
     5 using namespace std;
     6 
     7 const int maxn = 1000 + 10;
     8 const int maxm = 20;        //石子个数
     9 
    10 int f[maxm], sg[maxn];
    11 bool vis[maxn];
    12 //f[]: 可以取走的石子数量
    13 //sg[]: 1~n的sg函数值
    14 //vis[]: mex{}
    15 void getSG(int n) {
    16     memset(sg, 0, sizeof(sg));
    17     for (int i = 1; i <= n; i++) {
    18         memset(vis, 0, sizeof(vis));
    19         for (int j = 0; f[j] <= i && j < maxm; j++)
    20             vis[sg[i - f[j]]] = 1;
    21         for (int j = 0;; j++) if (!vis[j]) {   //最小的未出现的正整数
    22             sg[i] = j;
    23             break;
    24         }
    25     }
    26 }
    27 
    28 //记忆化搜索
    29 //f[]: 从小到大排序
    30 //sg[]: 初始化为-1
    31 //maxm,石子个数,集合的最大数量
    32 int dp(int x)
    33 {
    34     if (sg[x] != -1)  return sg[x];
    35     bool vis[maxn];
    36     memset(vis, 0, sizeof(vis));
    37     for (int i = 0; i < maxm; i++)
    38     {
    39         if (f[i] <= x)
    40         {
    41             dp(x - f[i]);
    42             vis[sg[x - f[i]]] = 1;
    43         }
    44     }
    45     for (int i = 0;; i++)
    46     {
    47         if (!vis[i])  return sg[x] = i;
    48     }
    49 }
    50 
    51 
    52 void init()
    53 {
    54     f[0] = f[1] = 1;
    55     for (int i = 2; i < maxm; i++)
    56         f[i] = f[i - 1] + f[i - 2];
    57     memset(sg, -1, sizeof(sg));
    58 }
    59 
    60 int m, n, p;
    61 
    62 int main()    
    63 {
    64     init();
    65     //getSG(1000);
    66     while (scanf("%d%d%d", &n, &m, &p) ==3 && n)
    67     {
    68         if (dp(m) ^ dp(n) ^ dp(p))  printf("Fibo
    ");
    69         else  printf("Nacci
    ");
    70     }
    71     return 0;
    72 }
    View Code

    参考链接:

    1、https://blog.csdn.net/yizhangbiao/article/details/51992022

    2、https://blog.csdn.net/strangedbly/article/details/51137432

  • 相关阅读:
    利用django form 模块处理post请求
    linux 下安装JDK
    java常用日期操作方法
    Git常见命令整理
    使用Java实现八种基本排序
    java验证类ValidUtils
    封装一个既能遍历数组又能遍历对象的的forEach函数
    结合canvas做雨滴特效
    前端常用js脚本
    canvas 视频音乐播放器
  • 原文地址:https://www.cnblogs.com/lfri/p/10663272.html
Copyright © 2011-2022 走看看