zoukankan      html  css  js  c++  java
  • Nim 博弈和 sg 函数

    sg 函数

    参考 通俗易懂
    论文

    几类经典的博弈问题

    1. 阶梯博弈: 只考虑奇数号楼梯Nim,若偶数楼梯只作容器,那么游戏变为Nim。题目
    2. 翻转硬币: 局面的SG值为局面中每个正面朝上的棋子单一存在时的SG值的异或和。题目
    3. Multi-SG游戏: 对于一个单一游戏,不同的方法可能会将其分成不同的多个单一游戏。每一方法对应的多个单一游戏的游戏的和即可以表示这种方法的NP状态。而这个单一游戏的SG函数即为未在所有方法的SG函数值中出现过的最小值。题目
    4. Anti-SG游戏和SJ定理 (在论文中有详细的论述和证明)
    5. Every-SG游戏: 对于Every-SG游戏先手必胜当且仅当单一游戏中最大的step为奇数。题目
    6. 树的删边游戏: 叶子节点的SG值为0;中间节点的SG值为它的所有子节点的SG值加1后的异或和。题目
    7. 无向图的删边游戏: 我们可以对无向图做如下改动:将图中的任意一个偶环缩成一个新点,任意一个奇环缩成一个新点加一个新边;所有连到原先环上的边全部改为与新点相连。这样的改动不会影响图的SG 值。

    题目

    hdu3032

    题意

    Multi-SG

    给 n 堆石子, 每次有两步可选的操作,

    1. 将一堆 石子分成两堆
    2. 拿走一堆石子的任意个

    最后不能行动的输。

    分析

    看完论文就会做了,

    一堆石子数量为2 时 的后继有:0,1和(1,1),他们的SG值分别为0,1,0,所以sg(2) =2。

    一堆石子数量为3 时的后继有:0、1、2、(1,2),他们的SG值分别为0、1、2、3,所以sg(3) = 4。

    一堆石子数量为4 时的后继有:0、1、2、3、(1,3)和(2,2),他们的SG值分别为0,1,2,4,5,0,所以sg(4) = 3.

    打表可以找到规律,见代码。

    code

    #include<cstdio>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const int MAXN = 1e5 + 5;
    const int INF = 1e9;
    
    int vis[30], sg[30];
    void init()
    {
        sg[0] = 0;
        for(int i = 0; i < 30; i++)
        {
            memset(vis, 0, sizeof vis);
            for(int j = 1; j < i; j++)
            {
                vis[sg[j] ^ sg[i - j]] = 1;
            }
            for(int j = 0; j < i; j++) vis[sg[j]] = 1;
            for(int j = 0; ; j++)
            {
                if(!vis[j])
                {
                    sg[i] = j;
                    printf("sg[%d]:%d
    ", i, j);
                    break;
                }
            }
        }
    }
    
    int main()
    {
        //init();
        int T;
        scanf("%d", &T);
        while(T--)
        {
            int n;
            scanf("%d", &n);
            int ans = 0;
            while(n--)
            {
                int x;
                scanf("%d", &x);
                if(x % 4 == 3) x++;
                else if(x % 4 == 0) x--;
                ans ^= x;
            }
            if(ans) puts("Alice");
            else puts("Bob");
        }
        return 0;
    }
    

    hdu3595

    题意

    一共有n个游戏,每一个游戏有两堆石子,一次移动可以从大的那堆石子里拿小的那堆石子的整数倍的石子。只要是可以操作的游戏都要进行操作,不能进行操作的人负。

    分析

    Every-SG游戏,对于Every-SG游戏先手必胜当且仅当单一游戏中最大的step为奇数,通过dfs找到必败态和必胜态的转移,对于必胜态,尽可能的慢结束游戏,对于必败态尽可能快的结束游戏。

    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int INF = 1e9;
    const int MAXN = 1e3 + 10;
    
    int dp[MAXN][MAXN];
    
    int dfs(int p, int q)
    {
        if(p > q) swap(p, q);
        if(!p) return dp[p][q] = 0;
        int k = q / p, tp = q % p, tq = p;
        int flag = dfs(tp, tq);
        if(k == 1)
        {
            dp[p][q] = dp[tp][tq] + 1;
            return flag ^ 1;
        }
        else
        {
            if(!flag) dp[p][q] = dp[tp][tq] + 1;
            else dp[p][q] = dp[tp][tq] + 2; // 后面存在一个不是终止态的必败态,这样至少会增加2步。
            return 1;
        }
    }
    
    int main()
    {
        int N;
        memset(dp, -1, sizeof dp);
        while(~scanf("%d", &N))
        {
            int ans = 0;
            while(N--)
            {
                int p, q;
                scanf("%d%d", &p, &q);
                dfs(p, q);
                ans = max(ans, dp[min(p, q)][max(p, q)]);
            }
            puts((ans & 1) ? "MM" : "GG");
        }
        return 0;
    }
    

    poj3710

    题意

    1. 有 N个局部联通的图。
    2. Harry 和 Sally轮流从图中删边,删去一条边后,不与根节点相连的部分将被移走。Sally为先手。
    3. 图是通过从基础树中加一些边得到的。
    4. 所有形成的环保证不共用边,且只与基础树有一个公共点。
    5. 谁无法移动谁输。

    分析

    树的删边游戏,参见论文。
    参考代码

    code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    using namespace std;
    typedef long long ll;
    const int INF = 1e9;
    const int MAXN = 1e3 + 10;
    
    int next[MAXN], head[MAXN], to[MAXN]; // 前向星
    int vis[MAXN]; // 标记数组
    // v[i]: 这里就解决了冲突,因为每添加一条边,其实是加入两条边,但是题目本身会存在重边的情况,注意到cnt初始化为1,第一条边是从2开始的,
    //       这样每经过一条边,会把相应的v[i]和v[i^1]置为1,正好使得每条边只会被访问一次而不和题目的重边冲突。
    int v[MAXN];
    int w[MAXN];   // 标记环
    int s[MAXN];   // 栈
    int cnt, top;
    int add(int u, int v)
    {
        to[++cnt] = v;
        next[cnt] = head[u];
        head[u] = cnt;
    }
    
    int dfs(int x)
    {
        vis[x] = 1;
        s[++top] = x;
        int ans = 0;
        for(int i = head[x]; i; i = next[i])
        {
            if(!v[i])
            {
                v[i] = 1; v[i ^ 1] = 1;
                int tmp = 0;
                if(!vis[to[i]]) tmp = dfs(to[i]) + 1;
                else
                {
                    int q = s[top--];
                    while(q != to[i])
                    {
                        w[q] = 1;
                        q = s[top--];
                    }
                    top++;
                    return 1;
                }
                if(w[to[i]]) ans ^= (tmp & 1); // 本来要判断环的奇偶性,回溯的时候是0,1交替,010101...
                else ans ^= tmp;
            }
        }
        return ans;
    }
    
    int main()
    {
        int n;
        while(~scanf("%d", &n))
        {
            int ans = 0;
            while(n--)
            {
                memset(next, 0, sizeof next);
                memset(head, 0, sizeof head);
                memset(vis, 0, sizeof vis);
                memset(v, 0, sizeof v);
                memset(w, 0, sizeof w);
                int m, k; cnt = 1; top = 0;
                scanf("%d%d", &m, &k);
                for(int i = 0; i < k; i++)
                {
                    int x, y;
                    scanf("%d%d", &x, &y);
                    add(x, y);
                    add(y, x);
                }
                ans ^= dfs(1);
            }
            if(ans) puts("Sally");
            else puts("Harry");
        }
        return 0;
    }
    
  • 相关阅读:
    移动Web框架:jQuery Mobile VS Sencha Touch
    2011最具争议性文章:中国网页设计为什么这么烂?
    如何学习嵌入式linux[转]
    嵌入式linux,老手给新手的建议
    开发菜鸟应该知道的十件事
    C#中二进制、八进制、十六进制和十进制之间的相互转化问题
    使用splitter控件 将界面分成可以调整宽度的三个部分
    Mutex 类
    Java 发邮件
    Java Servlet介绍 2
  • 原文地址:https://www.cnblogs.com/ftae/p/6791252.html
Copyright © 2011-2022 走看看