zoukankan      html  css  js  c++  java
  • 【博弈初步】

    基本常见的路人皆知的博弈

    巴什博奕(Bash Game);威佐夫博奕(Wythoff Game);尼姆博奕(Nimm Game)。 此外,还有翻硬币,删边等。 当然,不乏一些变态数学题。

    基础博弈高中是学习过,但是过于基础,现在强化博弈方面。

    一般的博弈最后取者胜。自然还有最后取者输的。

    博弈的输赢关键在于找到XXX态(必输、必赢、S态?T态之类的东西)。然后看能不能自己面对必赢态,然后取后把必输态留给对手。

     

    注意:一般的博弈题不能普通搜索(直接搜索是否可以再轮到自己时达到某状态)解决,因为两个玩家都是高智商,双方都是采取最优解。

    现在的状态应该是:自己可以将面对的状态变为必败,则为必赢;若不能则为必败。 若从一张图上看,就是G状态后序节点中有一个必败节点,则它自己为必胜节点.....然后可能就需要SG函数。

    【参考】

    博客经典讲解:http://blog.csdn.net/acm_cxlove/article/details/7854526 (cxlove)。

    SG函数详细:https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html

    SG函数通俗证明:http://blog.csdn.net/strangedbly/article/details/51137432

    二分图博弈:https://www.cnblogs.com/jffifa/archive/2011/12/17/2291069.html (2018-3-25补充)

    著名进阶讲解:Thomas S. Ferguson(群里有)。

     等做够了题,把难度分级后再上题。

    待续-------------------------------------------------------------------------

    【基础博弈】

    一:翻硬币游戏(Nim博弈) 

    题目大意:
    N个硬币排成一排,已知是正面(H)还是反面(T)。现在两个人轮流翻,每次只能选择一枚正面(H)的硬币翻到反面,
    同时可以选择一枚左边的硬币翻转(此时不管正反都可以翻)。 没有可以翻的人输。
    思路:
    我们规定,只有第i个硬币正面向上,则为状态i,全部向下为0。那么状态i可以变为状态0,1,2....i-1。分别表示0:翻转第i个,不翻转左边的;
    1:翻转第i个,顺便把第1个也翻转...
    但是情况不单单是只有一枚i硬币朝上。但是不重要,如果第j<i枚也朝上,那么翻转第i个,顺便翻转第j个的情况就是j^j=0就行了。
    所以状态X可以变为0,1,2,,,,x-1,这和Nim的规则是一样的。
    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int maxn=11000;
    char chr[maxn];
    int main()
    {
        int N,Xor,i;
        while(~scanf("%d",&N)){
            scanf("%s",chr+1);
            Xor=0;
            for(i=1;i<=N;i++){
                if(chr[i]=='H') Xor^=i;
            }
            if(Xor) printf("Alice
    ");
            else printf("Bob
    ");
        } return 0;
    }
    View Code

     二:John

    题意:和Nim一样的规则,但是是取到最后一个石子的人输。
    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    #include<cstring>
    using namespace std;
    int main()
    {
        int T,g,x,n,i,j;
        scanf("%d",&T);
        while(T--){
            scanf("%d",&n);
            x=g=0;
            for(i=1;i<=n;i++){
                scanf("%d",&j);
                x^=j; g+=(j>1?1:0);
            }
            if((!g&&x&1)||(g>1&&!x)) printf("Brother
    ");
            else printf("John
    ");
        } return 0;
    }
    View Code

    三:Be the Winner

    题意:有一排连续的石子,每次可以取任意连续的石子,取到最后一个的输。
    思路:参照上题
    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    #include<cstring>
    using namespace std;
    int main()
    {
        int g,x,n,i,j;
        while(~scanf("%d",&n)){
            x=g=0;
            for(i=1;i<=n;i++){
                scanf("%d",&j);
                x^=j; g+=(j>1?1:0);
            }
            if((!g&&x)||(g>1&&!x)) printf("No
    ");
            else printf("Yes
    ");
        } return 0;
    }
    View Code

    四:Being a Good Boy in Spring Festival

    题意:问Nim博弈中,先手胜的情况下,第一步有几种取石子方案。
    思路:实验一下每一堆即可。
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    using namespace std;
    int p[110];
    int main()
    {
        int n,Xor,i,ans;
        while(~scanf("%d",&n)){
            if(n==0) return 0;
            Xor=0;
            for(i=1;i<=n;i++){
                scanf("%d",&p[i]);  
                Xor^=p[i];
            }
            if(!Xor) printf("0
    ");
            else {
                ans=0;
                for(i=1;i<=n;i++){
                    if((Xor^p[i])<=p[i]) ans++;
                }
                printf("%d
    ",ans);
            }
        } return 0;
    }
    View Code

    五:A Simple Game

    题意:有N堆石子,Nim规则,但每一堆有上限Li,即单次不能超过Li个。 
    思路:每一堆mod(Li+1),即Xi>(Li+1)时,利用Bush博弈,后面的再Nim博弈。
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    using namespace std;
    int main()
    {
        int T,n,Xor,i,ans,M,L;
        scanf("%d",&T);
        while(T--){
            scanf("%d",&n);
            Xor=0;
            for(i=1;i<=n;i++){
                scanf("%d%d",&M,&L);  
                Xor^=(M%(L+1));
            }
            if(Xor) printf("No
    ");
            else printf("Yes
    ");
        } return 0;
    }
    View Code

    取石子游戏

    题意:先手不能一次性取完,下一个人取的数量不能超过上一位的两倍。
    思路:斐波拉契博弈。“Zeckendorf定理”(齐肯多夫定理):任意数可以表示位多个斐波拉契数的和,而且他们的位置不相邻。 先手每次取后变成的新数,表示成若干个斐波拉契
    数的和,从小到大(由于是不相邻的斐波拉契数,后面一个大于前面一个的两倍),
    后手都可以取某个斐波拉契数的最后一个。比如先手取后变成21,21=1+3+8;对于“1”,
    后手可以取到,对于“3”,先手不能一次性取完,无论怎么取,后手都拿到最后一个,对于“8”也是同理。对于每一个斐波拉契数数,都可以由后手取到。所以斐波拉契数后者胜。

    2,3是后手胜利,即斐波拉契数胜。 若不是斐波拉契数,先手可以取部分使之变成斐波拉契数,先手胜。
    #include<map>
    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    map<int,int>mp;
    int main()
    {
        int i,N,a[100];
        a[0]=2;a[1]=3;
        mp[2]=1; mp[3]=1;
        for(i=2;i<45;i++){
            a[i]=a[i-1]+a[i-2];
            mp[a[i]]=1;
        }
        while(~scanf("%d",&N)){
            if(N==0) return 0;
            if(mp.find(N)!=mp.end()) printf("Second win
    ");
            else printf("First win
    ");
        }
    } 
    View Code

    【其他博弈】

     一:Euclid's Game 

    题意:
    两人博弈,给出两个数a和b,较大数减去较小数的任意倍数,结果不能小于0,将两个数任意一个数减到0的为胜者。
    思路:假设 a大于b a == b.  N态 a%b == 0. N态 
             a >= 2*b,先手能决定谁取(b,a%b),并且知道(b,a%b)是P态还是N态.    N态
             b< a< 2*b, 只能 -->(b,a-b) , 然后再进行前面的判断.
    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    using namespace std;
    int main()
    {
        int num,a,b;
        while(~scanf("%d%d",&a,&b)){
            if(a==0&&b==0) return 0;
            num=1;
            while(true){
                if(a<b) swap(a,b);
                if(a%b==0||b==0) break;
                if(a>=2*b) break;
                a=a%b;
                num++;
            }
            if(num&1) printf("Stan wins
    ");
            else printf("Ollie wins
    ");
        } return 0;
    }
    View Code

     二:Good Luck in CET-4 Everybody!

    题意:有N个石子,每次可以取2的幂次个石子,即1,248...最后一个取到的胜利。
    思路:先手取后变成3的倍数,然后后手无论取什么,都变成不是3的倍数。 直到先手取后变成3,然后无论后手取1还是2,先手胜。
    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    #include<cstring>
    using namespace std;
    int main()
    {
        int n;
        while(~scanf("%d",&n)){
            if(n%3==0) printf("Cici
    ");
            else printf("Kiki
    ");
        } return 0;
    }
    View Code

     三:Play a game

    题意:两人在N*N的棋盘上走,起始在角落,轮流走,每次可以走到旁边一格,不能走重复的点,不能走动为输。
    思路:发现先手每次占据一个奇点位置(如1*1),然后后手只能占据一个偶点位置(如1*2,2*1),所以N为奇时,后手会被逼死。
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    int n,m; 
    int main()
    {
        while(~scanf("%d",&n)){
            if(n==0) return 0;
            if(n&1) printf("ailyanlu
    ");
            else printf("8600
    ");
        } return 0;
    }
    View Code

    四:游戏Game(二分图博弈)

    题意:给定一个矩阵棋盘,有些地方有阻碍不能走。先手选一点设为起点,然后从后手开始,一人走一步,走不动的输。
    思路:https://www.cnblogs.com/jffifa/archive/2011/12/17/2291069.html

     五:Moving Pebbles(对称博弈)

    题意:任选一堆,首先拿掉至少一个石头,然后移动任意个石子到任意一些堆中. 不能移动的输。n<=100000
    思路:利用对称性:注意题目中说的是移动任意石子到任意一些堆中,而非只能移动到一个堆(。。。我开始就这么以为,只要这里没有都错题,
    就不难想到后者可以模仿前一位的操作。

    【图论   SG】

    一:Fibonacci again and again(简单SG)

    题意:有3堆石子,每次可以从任意堆中 取出 F个(F是1,235813...),最后一个取的人胜利。
    思路:SG求出来,求异或和即可。
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    using namespace std;
    int f[20],SG[1010],ex[20];
    void solve()
    {
        for(int i=1;i<=1000;i++)
        {
            for(int j=1;j<=15;j++) {
                if(f[j]>i) break;
                ex[SG[i-f[j]]]=i;
            }
            for(int j=0;j<=1000;j++)  
               if(ex[j]!=i){
                 SG[i]=j;
                 break;
            }
        }
    }
    int main()
    {
        int n,m,p,i,j; f[1]=1;f[2]=2;
        for(i=3;i<=15;i++) f[i]=f[i-1]+f[i-2];
        solve();
        while(~scanf("%d%d%d",&n,&m,&p)){
            if(n==0&&m==0&&p==0) return 0;
            if(SG[n]^SG[m]^SG[p])  printf("Fibo
    ");
            else printf("Nacci
    ");
        } return 0;
    }
    View Code

     二取石子游戏 (SG打表找规律)

    题意:有N堆石子,每次可以选择在任意一堆取任意多的石子。也可以选择任意一堆,分成两堆。没有可以操作的选手输。
    思路:求出每一堆的SG函数,最后求异或和。 对于一堆有X个石子的堆,取后SG=:012...X-1;可以分:SG[1]^SG[X-1],,SG[2]^SG[X-2]...。
    我们打表出前面的SG函数,发现SG函数的规律是以4为周期变化的,具体为i,i+1,i+3,i+2
    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int maxn=20000;
    int sg[maxn];
    void solve()
    {
        for(int i=1;i<=maxn;i+=4){
            sg[i]=i; sg[i+1]=i+1;
            sg[i+2]=i+3; sg[i+3]=i+2;
        }
    }
    int main()
    {
        int N,i,x,Xor=0;
        solve(); scanf("%d",&N);
        for(i=1;i<=N;i++){
            scanf("%d",&x);
            Xor^=sg[x];
        }
        if(Xor) printf("Alice
    ");
        else printf("Bob
    ");
    }
    View Code

    三:kiki's game (SG打表找规律)

    题意:开始棋子在(n,m),每次可向左,或向上,或左上走一步。走到(1,1)者胜利。
    思路:打表出SG图找规律。发现n和m都为奇数点才是必胜N态。
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    int n,m; 
    int main()
    {
        while(~scanf("%d%d",&n,&m)){
            if(n==0&&m==0) return 0;
            n%=2; m%=2;
            if(n==0||m==0) printf("Wonderful!
    ");
            else printf("What a pity!
    ");
        } return 0;
    }
    View Code
  • 相关阅读:
    openstack 使用cloud init 和 console-log, nbd或者libguestfs 获取VM中的硬件信息。
    Unity doesn't load, no Launcher, no Dash appears
    ssh 应用
    设计感悟——产品的3个属性
    别让用户发呆—设计中的防呆的6个策略
    用户流失原因调研4步经
    5种方法提高你网站的登录体验
    浅谈当下7个网页设计趋势(转)
    适应各浏览器图片裁剪无刷新上传jQuery插件(转)
    C#操作Excel数据增删改查(转)
  • 原文地址:https://www.cnblogs.com/hua-dong/p/8397622.html
Copyright © 2011-2022 走看看