• 博弈论 && 题目


    终于我也开始学博弈了,说了几个月,现在才学。学多点套路,不深学。(~~)

    参考刘汝佳蓝书p132

    nim游戏:

    假设是两维的取石子游戏,每次可以在任意一堆拿任意数量个(至少一根,因为这样游戏的状态集有限,所以如果可以不拿的,那应该不是nim游戏了,大家都不拿,出现了bug),同时规定没有办法拿的那个人输。

    那么记录状态win[i][j]表示第一堆是i个,第二堆是j个的时候,是否必胜。则win[0][0] = false

    win[0][1--m] = true因为可以拿走全部,使得对手走去win[0][0]的必败状态。同理win[1---n][0] = true

    那么,定义:

    ①、一个状态是必败态当且仅当它的所有后继(也就是所有取石子方案)都是必胜态。  必败态是取什么都败

    ②、一个状态是必胜态当且仅当它至少有一个后继是必败态。   注意,必胜态不是任取都胜,是通过某种策略取了,然后胜利。

    ③、没有后继的状态是必败态(因为这里我们假设不能走就是输)

    有了上面的基础,

    学习了Chomp!游戏

    有一个n*m的矩阵,每次可以拿走(x, y)及其右上方的所有格子,谁拿到(1, 1)这个格子的输。

    // 嗯所以win[1][1] = false, win[1][2--m] = true, win[2---n][1] = true,然后我不会推了,

    这里用的是反证法,假设先手必败。但是我发现一个技巧就是,你只能假设他必败,因为定义1,必败态的所有后继都是必胜。如果你假设它必胜,那么只需要找到一个后继是必败就能证明,但这往往很难找。所以要假设它是必败的。

    那么如果先手必败,它取哪一个都是败,设是(n, m),这个时候后手必胜,它通过某种策略取到了必胜局面。那么事实上,先手在第一次取得时候就可以和后手这次取得一模一样,抢先进入必胜局面,这和假设矛盾。所以只有n==1&&m==1是输,其他赢。

    变种:约数游戏。

    有1--n个数字,两人轮流选择一个数,并且把这个数和它的约数全部删去,擦去最后一个的人赢。

    假设先手必败,就是它任取哪一个都败,假设我取了1,然后后手通过某种策略,擦去了x,使得进去必胜局面,那么其实这个局面先手可以抢先进入,因为1的所有数字的约数。所以先手永远必胜。

    nyoj 23 取石子(一)

    http://acm.nyist.net/JudgeOnline/problem.php?pid=23

    设f[0]是必败态,那么f[1--m]是必胜态,f[m + 1]的后继只有f[1---m],也就是留了一个必胜的后继给对手,所以f[m  + 1]是必败态。一路算下去,只要是m + 1的倍数的,都是必败态。

    原来这是巴什博奕(Bash Game):

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <assert.h>
    #define IOS ios::sync_with_stdio(false)
    using namespace std;
    #define inf (0x3f3f3f3f)
    typedef long long int LL;
    
    
    #include <iostream>
    #include <sstream>
    #include <vector>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #include <bitset>
    
    void work() {
        int n, m;
        scanf("%d%d", &n, &m);
        if (n <= m) {
            cout << "Win" << endl;
            return;
        }
        int base = m + 1;
        n %= base;
        if (n == 0) {
            cout << "Lose" << endl;
        } else cout << "Win" << endl;
    }
    
    int main() {
    #ifdef local
        freopen("data.txt", "r", stdin);
    //    freopen("data.txt", "w", stdout);
    #endif
        int t;
        scanf("%d", &t);
        while (t--) work();
        return 0;
    }
    View Code

    Nim和

    假设是取三堆石子,每堆石子至少取一个,可以取完,这样的游戏怎么快速解答,因为数字很大。

    设f(x1, x2, x3)表示三堆石子现在剩余个数的状态。

    L.Bouton定理:状态f(x1, x2, x3)是必败态的条件是:当且仅当x1 ^ x2 ^ x3 == 0,称为Nim sum

    这是单个游戏的结论,就是这个游戏就是给你三堆石子来玩。

    但是组合游戏:是给你很多个子游戏,每个子游戏就是三堆石子来玩。组合游戏都是很复杂的。注意你不能直接把x个子游戏里面的石头的值全部异或起来,然后判断nim和,这是不对的,因为每个游戏不互相影响。

    那么怎么判断整个组合游戏的局面呢?

    sg函数,怎么说呢,就是它的后继集合,第一个没有出现的非0整数。其实整个博弈过程就是一颗树。

    从状态f(x, y)--->【f(a, b), f(c, d), f(x, y)】都是它的后继,就是能直接到达的状态。每个状态又有后继。那么可以知道,没有后继的状态是必败的,因为你没得取石子了。记为0,那么它的父状态是必胜的,记为1,递归上去即可。

    一开始以为sg函数不是0就是1,其实是错的,因为一个状态有很多兄弟状态,如果兄弟状态的值是0、1、那么父状态就是2了。

    可以知道,一个状态为必败态的充要条件是sg值等于0.等于0说明它的后继没有0,它的后继没有0说明所有后继状态都是必胜态。

    所以父状态是必败的。

    sg定理:组合游戏和的sg函数等于各个子游戏sg函数的nim和。(nim和其实就是全部异或起来)

    组合游戏和的sg函数,也是一样,必败态的充要条件是sg值等于0。

    那么其实上面说的判断nim游戏的输赢,其实就是sg定理的直接应用。可以看出若干个一堆的nim游戏。

    假设一个nim游戏是三堆石子x, y, z,那么sg(x) = x的,是必胜的,因为一次全拿就是胜了。

    所以nim和就是sg(x) ^ sg(y) ^ sg(z),也即是x ^ y ^ z

    ---------------------------------------------------------------------------------------------------------------------------------

    nyoj 135 取石子(二)

    http://acm.nyist.net/JudgeOnline/problem.php?pid=135

    这题是n个的bash游戏。

    算出整个组合游戏的sg即可,单个游戏的sg值是n % (m + 1),可以先看看sg值找下规律。

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <assert.h>
    #define IOS ios::sync_with_stdio(false)
    using namespace std;
    #define inf (0x3f3f3f3f)
    typedef long long int LL;
    
    
    #include <iostream>
    #include <sstream>
    #include <vector>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #include <bitset>
    
    
    const int maxn = 50;
    int sg[maxn], vis[maxn];
    LL calc(LL n, LL m) {
        return n % (m + 1);
    }
    void work() {
        int n;
        cin >> n;
        LL res = 0;
        for (int i = 1; i <= n; ++i) {
            LL x, y;
            cin >> x >> y;
            if (x == 0 || y == 0) continue;
            res ^= calc(x, y);
        }
        if (res) {
            cout << "Win" << endl;
        } else cout << "Lose" << endl;
    //    sg[0] = 0;
    //    for (int i = 1; i <= n; ++i) {
    //        memset(vis, 0, sizeof vis);
    //        for (int j = 1; j <= min(i, m); ++j) vis[sg[i - j]] = 1;
    //        for (int j = 0; ; ++j) {
    //            if (!vis[j]) {
    //                sg[i] = j;
    //                break;
    //            }
    //        }
    //        printf("%d ", sg[i]);
    //    }
    }
    
    int main() {
    #ifdef local
        freopen("data.txt", "r", stdin);
    //    freopen("data.txt", "w", stdout);
    #endif
        int t;
        scanf("%d", &t);
        while (t--) work();
        return 0;
    }
    View Code

    注意,sg值是4说明必胜,sg值是1也是说明必胜,但是两者是不等价的。

    SG函数真的很好用,打表找下规律就马上出来了。

    A - The game of Osho Gym - 101147A  

    http://codeforces.com/gym/101147/problem/A

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <assert.h>
    #define IOS ios::sync_with_stdio(false)
    using namespace std;
    #define inf (0x3f3f3f3f)
    typedef long long int LL;
    
    
    #include <iostream>
    #include <sstream>
    #include <vector>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #include <bitset>
    
    
    const int maxn = 50;
    int sg[maxn * maxn];
    bool vis[maxn * maxn];
    int calc(int n, int k) {
        if (k > n) return n & 1;
        if (k & 1) return n & 1;
        else {
            int dis = k - 2 + 3;
            if (n > dis) n %= dis;
            if (n <= k - 2) {
                return n & 1;
            } else {
                n -= k - 2;
                return n % 3;
            }
        }
    }
    void calcSg() {
        int n, k;
        scanf("%d%d", &n, &k);
        sg[0] = 0;
        for (int i = 1; i <= n; ++i) {
            memset(vis, false, sizeof vis);
            int t = 1;
            for (t; t <= i; t *= k) {
                vis[sg[i - t]] = true;
                if (k == 1) break;
            }
            for (int j = 0; ; ++j) {
                if (!vis[j]) {
                    sg[i] = j;
                    break;
                }
            }
        }
        for (int i = 1; i <= n; ++i) {
            printf("%d ", sg[i]);
        }
        printf("
    ");
        cout << endl;
        cout << calc(n, k) << endl;
        cout << sg[n] << endl;
    }
    
    void work() {
        int has;
        int res = 0;
        scanf("%d", &has);
        while (has--) {
            int n, k;
            scanf("%d%d", &k, &n);
            res ^= calc(n, k);
    //        cout << n << " " << k << " " << calc(n, k) << " fff" << endl;
        }
        if (res) {
            printf("1
    ");
        } else printf("2
    ");
    }
    
    int main() {
    #ifdef local
        freopen("data.txt", "r", stdin);
    //    freopen("data.txt", "w", stdout);
    #endif
    //    calcSg();
        freopen("powers.in", "r", stdin);
        int t;
        scanf("%d", &t);
        while (t--)
            work();
        return 0;
    }
    View Code

    跪了,跪了一个晚上,做NYOJ 161 取石子 (四)

    http://acm.nyist.net/JudgeOnline/problem.php?pid=161

    想到了怎么打sg表,也打出来了,可是还是找不到规律。

    然后居然发现是威佐夫博奕,差值 * (1.618) == mi就是必败态。

    sg还是挺好打的,像以前一样枚举一维中取1、2、3、....、4。在第二维中取1、2、3、4、.....

    两维同时取1、2、3、4...5

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <assert.h>
    #define IOS ios::sync_with_stdio(false)
    using namespace std;
    #define inf (0x3f3f3f3f)
    typedef long long int LL;
    
    
    #include <iostream>
    #include <sstream>
    #include <vector>
    #include <set>
    #include <map>
    #include <queue>
    #include <string>
    #include <bitset>
    
    
    const int maxn = 100 + 20;
    int sg[maxn][maxn];
    bool vis[maxn * maxn];
    int a, b;
    void calcSg() {
        int n, m;
        cin >> n >> m;
        for (int i = 0; i <= n; ++i) {
            sg[i][0] = 0 ^ i;
        }
        for (int i = 0; i <= m; ++i) {
            sg[0][i] = 0 ^ i;
        }
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
    //            sg[i][j] = i ^ j;
                memset(vis, 0, sizeof vis);
                for (int t = 1; t <= min(i, j); ++t) {
                    vis[sg[i - t][j - t]] = true;
                }
                for (int t = 1; t <= i; ++t) {
                    vis[sg[i - t][j]] = true;
                }
                for (int t = 1; t <= j; ++t) {
                    vis[sg[i][j - t]] = true;
                }
                for (int t = 0; ; ++t) {
                    if (!vis[t]) {
                        sg[i][j] = t;
                        break;
                    }
                }
            }
        }
        for (int i = 0; i <= n; ++i) {
            for (int j = 0; j <= m; ++j) {
                printf("%4d ", sg[i][j]);
    //            cout << i << " " << j << " " << sg[i][j] << endl;
            }
            cout << endl;
        }
    }
    void work() {
        const double res = (sqrt(5.0) + 1) / 2;
        int mi = min(a, b);
        int mx = max(a, b);
        int dis = mx - mi;
        if ((int)(dis * res) == mi) {
            cout << "0" << endl;
        } else cout << "1" << endl;
    }
    
    
    
    int main() {
    #ifdef local
        freopen("data.txt", "r", stdin);
    //    freopen("data.txt", "w", stdout);
    #endif
    //    calcSg();
        while (cin >> a >> b && (a + b)) work();
        return 0;
    }
    View Code
  • 相关阅读:
    【面向对象-天龙八部】
    【面向对象-作业】
    【OOP】面向对象的程序开发
    nmcli命令详解(创建热点,连接wifi,管理连接等)
    构造函数&&继承8.1
    Set集合之TreeSet类
    Set集合之HashSet类
    HDFS上传数据的流程
    zookeeper的安装与配置
    hive介绍、安装配置、表操作基础知识适合小白学习
  • 原文地址:https://www.cnblogs.com/liuweimingcprogram/p/6755707.html
Copyright © 2020-2023  润新知