zoukankan      html  css  js  c++  java
  • SG函数与例题

    int sg[maxn];//sg[n] n表示每堆数量
    int s[k];//每次能取的值,下标从0开始,0 ~ k-1,必须有序,可以sort(s,s+k);
    bool vis[maxn];
    const int k;//k是集合s的大小 
    
    void get_sg()
    {
        int i,j;
        for(i=0;i<maxn;i++)
        {
            memset(vis,0,sizeof(vis));
            j=0;
            while(j<k&&s[j]<=i)
            {
              vis[sg[i-s[j]]]=1;
            j++;
            }
            for(j=0;j<maxn;j++)
            if(!vis[j])
            {
                sg[i]=j;
                break;
            }
        }
    }
    
    int main()
    {
        ...    //将s数组初始化,如果s数组不是从小到大递增的话需要进行sort
        memset(sg,-1,sizeof(sg));
        get_sg();
        if(sg[n]==0) //先手必败
        else    //先手必胜
    
       //如果有多堆,则
       // num=sg[n1]^sg[n2]^sg[n3]^....^sg[nx];
       // if(num==0) 则先手必败
       // else    先手必胜
        ...
    }
    //注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍
    //n是集合s的大小 S[i]是定义的特殊取法规则的数组
    int s[110],sg[10010],n;
    int SG_dfs(int x)
    {
        int i;
        if(sg[x]!=-1)
            return sg[x];
        bool vis[110];
        memset(vis,0,sizeof(vis));
        for(i=0;i<n;i++)
        {
            if(x>=s[i])
            {
                SG_dfs(x-s[i]);
                vis[sg[x-s[i]]]=1;
            }
        }
        int e;
        for(i=0;;i++)
            if(!vis[i])
            {
                e=i;
                break;
            }
        return sg[x]=e;
    }

    Good Luck in CET-4 Everybody!

     HDU - 1847 

    题意:n 张牌( 1 <= n <= 1000),两个人轮流取牌,只能取2的幂次张牌 (即:1,2,4,8,16...),最后抓完牌的人获胜

    题解:一堆,s数组取值为 2 ^ i (0 <= i <= 11)

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<stack>
    #include<cstdlib>
    #include<queue>
    #include<set>
    #include<string.h>
    #include<vector>
    #include<deque>
    #include<map>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define eps 1e-4
    #define bug printf("*********
    ")
    #define debug(x) cout<<#x"=["<<x<<"]" <<endl
    typedef long long LL;
    typedef long long ll;
    const int maxn = 1e3 + 5;
    const int mod = 998244353;
    
    #define k 12
    int sg[maxn];//sg[n] n表示每堆数量
    int s[k];//每次能取的值,下标从0开始,0 ~ k-1,必须有序,可以sort(s,s+k);
    bool vis[maxn];
    
    
    void get_sg() {
        int i, j;
        for (i = 0; i < maxn; i++) {
            memset(vis, 0, sizeof(vis));
            j = 0;
            while (j < k && s[j] <= i) {
                vis[sg[i - s[j]]] = 1;
                j++;
            }
            for (j = 0; j < maxn; j++)
                if (!vis[j]) {
                    sg[i] = j;
                    break;
                }
        }
    }
    
    int main()
    {
        int n;
        for(int i = 0; i < k; i++)
            s[i] = int(pow(2,i));
        while(cin >> n) {
            memset(sg, -1, sizeof(sg));
            get_sg();
            if (sg[n] == 0) //先手必败
                puts("Cici");
            else
                puts("Kiki");//先手必胜
        }
    
        //如果有多堆,则
        // num=sg[n1]^sg[n2]^sg[n3]^....^sg[nx];
        // if(num==0) 则先手必败
        // else    先手必胜
    
    }
    View Code

     Northcott Game

     HDU - 1730 

    题意:如图所示,游戏在一个n行m列(1 ≤ n ≤ 1000且2 ≤ m ≤ 100)的棋盘上进行,每行有一个黑子(黑方)和一个白子(白方)。执黑的一方先行,每次玩家可以移动己方的任何一枚棋子到同一行的任何一个空格上,当然这过程中不许越过该行的敌方棋子。双方轮流移动,直到某一方无法行动为止,移动最后一步的玩家获胜。

    题解:每一行中加的距离可以看作是石子的堆数,因为每一个当一个人往两侧走的时候,另一个人可以往那个方向走直至距离一样,这个基础上它还可以再走,所以中间的距离只会减少不会增加,就可以看作是一个多堆石子的NIM游戏

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<stack>
    #include<cstdlib>
    #include<queue>
    #include<set>
    #include<string.h>
    #include<vector>
    #include<deque>
    #include<map>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define eps 1e-4
    #define bug printf("*********
    ")
    #define debug(x) cout<<#x"=["<<x<<"]" <<endl
    typedef long long LL;
    typedef long long ll;
    const int maxn = 1e3 + 5;
    const int mod = 998244353;
    
    #define k 12
    int sg[maxn];//sg[n] n表示每堆数量
    int s[k];//每次能取的值,下标从0开始,0 ~ k-1,必须有序,可以sort(s,s+k);
    bool vis[maxn];
    
    
    void get_sg() {
        int i, j;
        for (i = 0; i < maxn; i++) {
            memset(vis, 0, sizeof(vis));
            j = 0;
            while (j < k && s[j] <= i) {
                vis[sg[i - s[j]]] = 1;
                j++;
            }
            for (j = 0; j < maxn; j++)
                if (!vis[j]) {
                    sg[i] = j;
                    break;
                }
        }
    }
    
    int main()
    {
        int n,m;
        while(cin >> n >> m) {
            int a,b;
            int flag = 0;
            while(n--){
                cin >> a >> b;
                flag ^= (abs(a - b) - 1);
            }
            if(flag == 0) puts("BAD LUCK!");
            else puts("I WIN!");
        }
    
        //如果有多堆,则
        // num=sg[n1]^sg[n2]^sg[n3]^....^sg[nx];
        // if(num==0) 则先手必败
        // else    先手必胜
    
    }
    View Code

     Fibonacci again and again

     HDU - 1848 

    题意:3堆石子,每次只能取斐波那契数列中的个数,去完最后一个的人赢

    题解:打表fibonacci后打SG表

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<stack>
    #include<cstdlib>
    #include<queue>
    #include<set>
    #include<string.h>
    #include<vector>
    #include<deque>
    #include<map>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define eps 1e-4
    #define bug printf("*********
    ")
    #define debug(x) cout<<#x"=["<<x<<"]" <<endl
    typedef long long LL;
    typedef long long ll;
    const int maxn = 1e3 + 5;
    const int mod = 998244353;
    
    int sg[maxn];//sg[n] n表示每堆数量
    int s[50];//每次能取的值,下标从0开始,0 ~ k-1,必须有序,可以sort(s,s+k);
    bool vis[maxn];
    int k;//k是集合s的大小
    
    void get_sg()
    {
        int i,j;
        for(i=0;i<maxn;i++)
        {
            memset(vis,0,sizeof(vis));
            j=0;
            while(j<k&&s[j]<=i)
            {
                vis[sg[i-s[j]]]=1;
                j++;
            }
            for(j=0;j<maxn;j++)
                if(!vis[j])
                {
                    sg[i]=j;
                    break;
                }
        }
    }
    
    int main()
    {
        s[0] = 1;
        s[1] = 2;
        for(int i = 2;i ; i++){
            s[i] =  s[i - 1] + s[i - 2];
            if(s[i] > 1000) {
                k = i;
                break;
            }
        }
    //    debug(k);
        int m,n,p;
        while(cin >> m >> n >> p) {
            if(m == 0 && n == 0 && p == 0)
                break;
            memset(sg, -1, sizeof(sg));
            get_sg();
            int flag = sg[n] ^ sg[m] ^ sg[p];
            if(flag == 0) puts("Nacci");
            else puts("Fibo");
    
        }
        //如果有多堆,则
        // num=sg[n1]^sg[n2]^sg[n3]^....^sg[nx];
        // if(num==0) 则先手必败
        // else    先手必胜
    
    }
    View Code

     S-Nim

     HDU - 1536 

    题意:给出k个可取数的集合,m个查询。查询每次是否是先后获胜。

    题解:给定s数组,记得排序

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<stack>
    #include<cstdlib>
    #include<queue>
    #include<set>
    #include<string.h>
    #include<vector>
    #include<deque>
    #include<map>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define eps 1e-4
    #define bug printf("*********
    ")
    #define debug(x) cout<<#x"=["<<x<<"]" <<endl
    typedef long long LL;
    typedef long long ll;
    const int MAXN = 1e4 + 5;
    const int mod = 998244353;
    
    
    int sg[MAXN];
    int s[MAXN];
    bool vis[MAXN];
    int k;
    void SG() {
        int i,j;
        for(i = 0 ; i < MAXN; i ++) {
            memset(vis,0,sizeof vis);
            j = 0;
            while(j < k && s[j] <= i) {
                vis[sg[i - s[j]]] = 1;
                j++;
            }
            for(j = 0; j < MAXN; j++)
                if(!vis[j]) {
                    sg[i] = j;
                    break;
                }
        }
    }
    int main()
    {
        int n;
        while(scanf("%d",&n) && n) {
            memset(sg,-1,sizeof sg);
            for(int i = 0; i < n; i++)
                scanf("%d",&s[i]);
            k = n;
            sort(s,s + k);
            SG();
            int m;
            scanf("%d",&m);
            while(m--) {
                int l;
                scanf("%d",&l);
                int flag = 0;
                while(l--) {
                    int tmp;
                    scanf("%d",&tmp);
                    flag ^= sg[tmp];
                }
                if(flag == 0) putchar('L');
                else putchar('W');
            }
            printf("
    ");
        }
    }
    View Code

    Game of Hyper Knights (二维图中dfs打表sg函数)

     LightOJ - 1315 

    题意:给定一个棋盘,左上角为(0,0),棋盘中有多个骑士,每一个骑士只能按照图中的6种方式移动,两个人轮流移动棋盘中任意一个骑士,当轮到某一个人移动骑士时,棋盘中的骑士都已经不能移动了则判定为输,Alice先移动棋盘中的骑士,最后输出Alice和Bob谁赢谁输。

    题解:典型的博弈SG函数,对每一个骑士的起始位置求SG值,然后将所有的SG值进行异或,如果其值为0,则先手必败,即Bob 获胜,否则先手必胜,Alice获胜。又由于这道题是二维的,因此每一个位置都是由x和y两个值来决定的,因此这道题无法使用打表的方式进行求SG值,需要时dfs的方式,SG初始化为-1即可。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<stack>
    #include<cstdlib>
    #include<queue>
    #include<set>
    #include<string.h>
    #include<vector>
    #include<deque>
    #include<map>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define eps 1e-4
    #define bug printf("*********
    ")
    #define debug(x) cout<<#x"=["<<x<<"]" <<endl
    typedef long long LL;
    typedef long long ll;
    const int MAXN = 1e3 + 5;
    const int mod = 998244353;
    
    int s[6][2] = {{1,-2},{-1,-3},{-1,-2},{-2,-1},{-3,-1},{-2,1}};
    int sg[MAXN][MAXN];
    
    int SG_dfs(int x,int y) {
        if(sg[x][y] != -1)
            return sg[x][y];
        bool vis[MAXN];
        memset(vis,0,sizeof vis);
        for(int i = 0; i < 6; i++) {
            int nx = x + s[i][0];
            int ny = y + s[i][1];
            if(nx >= 0 && ny >= 0) {
                SG_dfs(nx,ny);
                vis[sg[nx][ny]] = 1;
            }
        }
        int e;
        for(int i = 0 ; ; i++) {
            if(!vis[i]) {
                e =  i;
                break;
            }
        }
        return sg[x][y] = e;
    }
    int main()
    {
        int ca = 1;
        int t;
        scanf("%d",&t);
        memset(sg,-1,sizeof sg);
        while(t--) {
            int n;
            scanf("%d",&n);
            int flag = 0;
            while(n--)
            {
                int x,y;
                scanf("%d %d",&x,&y);
                flag ^= SG_dfs(x,y);
            }
            printf("Case %d: %s
    ",ca++,flag == 0 ? "Bob" : "Alice");
        }
    }
    View Code

    F - Game of Cards

     Gym - 101128G 

    题意:有n堆扑克牌,两个人轮流玩游戏,游戏规则:先选一堆扑克牌,然后拿走堆顶0-k张,剩余的堆顶那一张牌上是几就必须再拿走几张,当某一方无牌可拿或者剩余张数不够必须拿走的张数时则该方输。

    题解:sg,s数组为0 - k,打sg表的时候还要把最少面的牌去掉

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<stack>
    #include<cstdlib>
    #include<queue>
    #include<set>
    #include<string.h>
    #include<vector>
    #include<deque>
    #include<map>
    using namespace std;
    #define INF 0x3f3f3f3f
    #define eps 1e-4
    #define bug printf("*********
    ")
    #define debug(x) cout<<#x"=["<<x<<"]" <<endl
    typedef long long LL;
    typedef long long ll;
    const int MAXN = 1e3 + 5;
    const int mod = 998244353;
    
    int sg[MAXN];
    int pile[MAXN];
    bool vis[MAXN];
    int k;
    
    void SG(int m)
    {
        memset(sg, 0, sizeof(sg));
        for (int i = 1; i <= m; i++)
        {
            memset(vis, false, sizeof(vis));
            int j = 0;
            while(j <= k && j <= i) {
                if(i == j){
                    j++;
                    continue;
                }
                int t = i - j - pile[i - j];
                if(t >= 0)
                    vis[sg[t]] = 1;
                j++;
            }
            for(j =0 ; j <= m; j++ ) {
                if(!vis[j]) {
                    sg[i] = j;
                    break;
                }
            }
        }
    }
    int main()
    {
        int n;
        cin >> n >> k;
    
        int flag = 0;
        while(n--)
        {
            int m;
            cin >> m;
            for(int i = 1; i <= m; i++)
                cin >> pile[i];
            SG(m);
            flag ^= sg[m];
        }
        if(flag == 0) puts("Bob will win.");
        else puts("Alice can win.");
    }
    
    /*
    4 1
    4
    1 1 1 1
    6
    2 1 2 1 2 1
    4
    1 1 1 1
    6
    2 1 2 1 2 1
    
    
    2 1
    1 1
    3 1 2 1
    
     */
    View Code
  • 相关阅读:
    mysql修改登录密码三种方式
    python 面向对象类与类之间的关系
    python 初识函数
    python 编码
    MVC部门树的实现 http://www.ztree.me/v3/api.php
    事务能否对一个表进行如下操作:先删除后添加?
    添加注释时,该如何输入当前修改时间呢
    js代码折叠的方法//#region 代码 //#endregion
    echarts画折线图,柱状图,饼图
    方法中开启一个事务之后,能否调用另一个通过事务实现的函数?
  • 原文地址:https://www.cnblogs.com/smallhester/p/11371983.html
Copyright © 2011-2022 走看看