• 博弈论学习笔记


    决定近段时间复习一下博弈论顺便写点笔记。

    大佬博客:几种常见博弈模型https://blog.csdn.net/wr132/article/details/51213331

    SG函数与SG定理https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html

    无敌的博弈总结https://blog.csdn.net/acm_cxlove/article/details/7854526

    常见博弈模型

    首先要清楚博弈论的基础知识概念(必胜必败态,多堆游戏,公平组合游戏,有向图游戏),然后了解几种常见的博弈模型,其中中比较常见的应该就是巴什博弈和尼姆博弈了。把一些题目转换为经典的博弈论模型能够快速地找到规律解决问题。

    巴什博弈:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜。n%(m+1)=0,是必先手败的局势。

    威佐夫博奕:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。奇异局势:ak =[k(1+√5)/2],bk= ak + k先手必败。

    尼姆博弈:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。(a,b,c)是必败态等价于a^b^c=0。

     HDU-2149

    巴什博弈裸题,问是否先手必胜,是的话输出先手第一次出手的必胜策略。m%(n+1)不为0先手必胜,必胜策略分两种如果m<=n那么先手就可以一次胜利,否则就第一次就要出m%(n+1)。

    #include<bits/stdc++.h>
    using namespace std;
    
    int main()
    {
        int m,n;
        while (scanf("%d%d",&m,&n)==2) {
            if (m%(n+1)==0) {
                puts("none"); continue;
            }
            if (m<=n) {
                printf("%d",m); for (int i=m+1;i<=n;i++) printf(" %d",i);
            } else {
                printf("%d",m%(n+1));
            }
            
            puts("");
        }
        return 0;
    }
    View Code

    POJ-1067

    威佐夫博奕裸题,只有奇异局势的时候是必败态,奇异局势的公式为 ak =[k(1+√5)/2],bk= ak + k 。我们怎么判断呢?用bk-ak得到k,然后用这个k计算得到ak看看和原ak是否相等。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    
    int main()
    {
        int n,m;
        while (scanf("%d%d",&n,&m)==2) {
            if (m>n) swap(n,m);
            int k=n-m;
            int tmp=floor(k*(1.0+sqrt(5.0))/2);
            if (tmp==m) puts("0"); else puts("1");
        }
        return 0;
    }
    View Code

    HDU-1847

    巴什博弈变形。我们观察到3是一个先手必败点,所以大家都避免走到3,但是只要有一个人避免了必败点他就可以通过对方出的数凑成3的倍数让对方必败。所以n是3的倍数时候先手必败否则先手必胜。

    #include<bits/stdc++.h>
    using namespace std;
    
    int main()
    {
        int n;
        while (scanf("%d",&n)==1) {
            if (n%3) puts("Kiki"); else puts("Cici");
        }
        return 0;
    }
    View Code

     SG函数与SG定理

    但是对于一些非常见博弈模型或者是模型变形规律不明显的,我们有一个大杀器:SG函数。通过SG函数我们能够快速直到某个点是必胜/必败态从而找到规律。

    做了HDU-1848当作求SG函数的模板:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int N=1e3+10;
     4 int f[30],S[N],SG[N]; 
     5 
     6 void getSG(int n) {
     7     memset(SG,0,sizeof(SG));
     8     for (int i=1;i<=n;i++) {
     9         memset(S,0,sizeof(S));  //后继SG状态函数集合 
    10         for (int j=0;j<=20&&f[j]<=i;j++) S[SG[i-f[j]]]=1;  //标记后继状态SG函数 
    11         for (int j=0;;j++) 
    12             if (!S[j]) {  //mex:后继SG第一个没出现的 
    13                 SG[i]=j; break;
    14             }
    15     }
    16 }
    17 
    18 int main()
    19 {
    20     int n,m,k;
    21     f[0]=f[1]=1;
    22     for (int i=2;i<=20;i++) f[i]=f[i-1]+f[i-2];  //求出可操作集 
    23     getSG(1000);  //计算前1000的SG函数 
    24     while (scanf("%d%d%d",&n,&m,&k) && (n||m||k)) {
    25         if (SG[n]^SG[m]^SG[k]) printf("Fibo
    ");  //多堆游戏XOR和 
    26         else printf("Nacci
    ");
    27     }
    28     return 0;
    29 }
    View Code

     HDU-1536

    SG函数直接应用。要注意这题输入的操作集合不是升序的要先排序才行。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e4+10;
    int k,n,m,f[110],S[N],SG[N]; 
    
    void getSG(int n) {
        memset(SG,0,sizeof(SG));
        memset(S,-1,sizeof(S));
        for (int i=1;i<=n;i++) {
            for (int j=1;j<=k&&f[j]<=i;j++) S[SG[i-f[j]]]=i;  
            for (int j=0;;j++) 
                if (S[j]!=i) {  
                    SG[i]=j; break;
                }
        }
    }
    
    int main()
    {
        while (scanf("%d",&k) && k) {
            for (int i=1;i<=k;i++) scanf("%d",&f[i]);
            sort(f+1,f+k+1);
            getSG(10000);
            scanf("%d",&n);
            for (int i=1;i<=n;i++) {
                int m,tmp=0; scanf("%d",&m);
                for (int j=1;j<=m;j++) {
                    int x; scanf("%d",&x);
                    tmp^=SG[x];
                }
                if (tmp==0) printf("L"); else printf("W"); 
            }
            puts("");
        }
        return 0;
    }
    View Code

    HDU-3980

    这道题会难一些。首先原来是一个环不好处理,但是我们注意到只要第一步下手后就会变成链,所以我们一开始从n-m这条链开始求SG函数。那么假设此时是一条长度为n的链,我们在当前选手在上面选择了长度为m的区间染色,之后这条链就会分割成两段分别是i和n-i-m的后继状态,于是此时就会变成两个游戏,然后根据多堆Nim博弈异或和的结论,后继状态的SG函数就是SG[i]^SG[n-i-m],所以我们就能标记所有后继的SG函数得到当前状态的SG函数。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e3+10;
    int n,m,SG[N],S[N];
    
    int getSG(int n) {
        if (n<m) return 0;
        if (SG[n]!=-1) return SG[n];
        for (int i=0;n-i-m>=0;i++) 
            S[getSG(i)^getSG(n-i-m)]=n;  //Nim博弈 
        for (int i=0;;i++)
            if (S[i]!=n) {
                SG[n]=i; break;
            } 
        return SG[n];    
    }
    
    int main()
    {
        int T,Case=0; cin>>T;
        while (T--) {
            scanf("%d%d",&n,&m);
            memset(SG,-1,sizeof(SG));
            memset(S,-1,sizeof(-1));
            SG[0]=0;
            if (n<m || getSG(n-m)) printf("Case #%d: abcdxyzk
    ",++Case);
            else printf("Case #%d: aekdycoin
    ",++Case);
        }
        return 0;
    }
    View Code

     POJ-2505

    SG函数应该很容易求。但是此题的数字非常大数组决定存不下,可以考虑用map计算SG函数可以获得AC。

    #include<cstdio>
    #include<iostream>
    #include<map>
    using namespace std;
    typedef long long LL;
    LL n;
    map<LL,LL> SG;
    
    LL getSG(LL x) {
        if (x>=n) return 0;
        if (SG.count(x)) return SG[x];
        map<LL,int> S;
        for (int i=2;i<=9;i++) S[getSG(x*i)]=1;
        for (int i=0;;i++)
            if (!S.count(i)) return SG[x]=i;
    }
    
    int main()
    {
        while (scanf("%lld",&n)==1) {
            SG.clear();
            if (getSG(1)) printf("Stan wins.
    "); else printf("Ollie wins.
    ");
        }
        return 0;
    }
    View Code

    其他题目练习:

    POJ-2484

    很经典的一道题。给n个连成环的硬币,每次每人可以取一个或者相邻的两个硬币,最后取不到硬币的人输掉。这题要运用一种平均分然后不断模仿对方决策得到必胜的思想。首先如果硬币个数<=2先手必胜,大于3的时候第一个人取完之后环会变成一条链,然后无论第一个人取的是一个还是两个第二个人都可以根据第一个人取的数量和位置做出“把剩下硬币取成完全相同的两条链”(这里要重点理解)。然后通过模仿对方达到不败之地。所以硬币数量>=3时候先手必败。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N=1e6+10;
    int n,m;
    
    int main()
    {
        while (scanf("%d",&n)==1 && n) {
            if (n<=2) puts("Alice");
            else puts("Bob");
        }
        return 0;
    }
    View Code

    POJ-2425

    图上的博弈:一个n个点的有向无环图,有某些点有硬币,每次没人只可以把一个棋子往前推一步(当然是沿着边的方向推),最后不能推的人输掉。尽管是在DAG上的博弈,但是SG函数的求法还是那样,标记所有后继状态之后mex。然后有几个棋子就是XOR和即可。

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #include<cstring>
    using namespace std;
    const int N=1e3+10;
    int n,m,q,SG[N];
    vector<int> G[N];
    
    int getSG(int x) {
        if (SG[x]!=-1) return SG[x];
        int S[N]={0};
        for (int i=0;i<G[x].size();i++) {
            int y=G[x][i];
            S[getSG(y)]=1;
        }
        for (int i=0;;i++)
            if (S[i]!=1) return SG[x]=i;
    }
    
    int main()
    {
        while (scanf("%d",&n)==1) {
            for (int i=1;i<=n;i++) {
                G[i].clear();
                int t; scanf("%d",&t);
                for (int j=1;j<=t;j++) {
                    int x; scanf("%d",&x); x++;
                    G[i].push_back(x);
                }
            }
            memset(SG,-1,sizeof(SG));
            while (scanf("%d",&q) && q) {
                int ans=0;
                for (int j=1;j<=q;j++) {
                    int x; scanf("%d",&x); x++;
                    ans^=getSG(x);
                }
                printf("%s
    ",ans?"WIN":"LOSE");
            }
        }
        return 0;
    }
    View Code

    POJ 1704 Georgia and Bob

    一列格子上有n个棋子,每次没人可以选择一个棋子往左移动若干步,棋子不能越过1格子,棋子也不能越过其他棋子移动,不能移动的人输掉。这题真的挺巧妙的,听大佬说这是一种叫阶梯博弈的博弈模型。我们先考虑对于一组相邻的棋子,这种情况肯定是先手必败,因为先手移动x格同时后手可以用后面那个棋子移动相同格。那么我们把棋子两两配对,那么两两配对的时候前面棋子的移动其实是没有意义的,因为与其配对的后面棋子可以做一模一样的操作。所以唯一的变数变成了两两配对的一组棋子间的间隙。

    所以解法就是把棋子两两匹配,奇数时候第一个棋子和格子1匹配,然后对于每一组格子间的间隙当成一堆Nim游戏,求出所有Nim游戏的和就是答案。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N=1e3+10;
    int n,a[N];
    
    int main()
    {
        int T; cin>>T;
        while (T--) {
            scanf("%d",&n);
            for (int i=1;i<=n;i++) scanf("%d",&a[i]);
            sort(a+1,a+n+1);
            int ans=0;
            for (int i=n;i>0;i-=2) ans^=a[i]-a[i-1]-1;
            printf("%s
    ",ans?"Georgia will win":"Bob will win");
        }
        return 0;
    }
    View Code


    POJ 1740 A New Stone Game
    POJ 2068 Nim
    POJ 3480 John
    POJ 2348 Euclid's Game
    HOJ 2645 WNim
    POJ 3710 Christmas Game 
    POJ 3533 Light Switching Game

    HOJ 4388 Stone Game II

    ZJU 3057 beans game

  • 相关阅读:
    Tomcat系列(10)——Tomcat主要设计模式5种(外观,责任链,观察者,模板方法,命令模式)
    Tomcat系列(9)——Tomcat 6方面调优(内存,线程,IO,压缩,缓存,集群)
    Tomcat系列(8)——Tomcat运行模式连接数和线程池
    Tomcat系列(7)——Tomcat类加载机制
    Tomcat系列(6)——Tomcat处理一个HTTP请求的过程
    Tomcat系列(5)——Tomcat配置详细部分
    Tomcat系列(4)——Tomcat 组件及架构详细部分
    Tomcat系列(3)——Tomcat 组件及架构核心部分 4类主要组件(顶层,连接器,容器,嵌套)
    Tomcat系列(2)——Tomcat文件目录7个
    Tomcat系列(1)——Tomcat安装配置
  • 原文地址:https://www.cnblogs.com/clno1/p/11231919.html
Copyright © 2020-2023  润新知