zoukankan      html  css  js  c++  java
  • (转)博弈汇总

    原文链接:http://www.cnblogs.com/frog112111/p/3199073.html

    一、介绍概念:

     P点 即必败点,某玩家位于此点,只要对方无失误,则必败;

    N点 即必胜点,某玩家位于此点,只要自己无失误,则必胜。

    定理:

            (1) 所有终结点都是必败点P(上游戏中,轮到谁拿牌,还剩0张牌的时候,此人就输了,因为无牌可取);

            (2)所有一步能走到必败点P的就是N点;

            (3)通过一步操作只能到N点的就是P点;

    首先来玩个游戏,引用杭电课件上的:

    (1) 玩家:2人;

    (2) 道具:23张扑克牌;

    (3) 规则:

    游戏双方轮流取牌;

    每人每次仅限于取1张、2张或3张牌;

    扑克牌取光,则游戏结束;

    最后取牌的一方为胜者

        自己画下图(N-P图)看看。

        x :  0  1  2  3  4  5  6  7  8  9  10。。。

        pos:P   N N  N  P N  N  N  P N   N 。。。

        所以若玩家甲位于N点。只要每次把P点让给对方,则甲必胜;

        反之,若玩家甲位于P点,他每次只能走到N点,而只要乙每次把P点让给甲,甲必败;

       hdu  2147   2188   2149   1847

    二、巴什博奕(Bash Game):

         只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。

       显然,如果n=m+1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜。因此我们发现了如何取胜的

    法则:如果n=(m+1)r+s,(r为任意自然数,s≤m),那么先取者要拿走s个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)

    (r-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。

    hdu  1846   

    AC代码如下:

     1 #include<stdio.h>
     2 int main()
     3 {
     4     int t,n,m;
     5     scanf("%d",&t);
     6     while(t--)
     7     {
     8         scanf("%d %d",&n,&m);
     9         if(n%(m+1)==0)
    10             printf("second
    ");
    11         else
    12             printf("first
    ");
    13     }
    14     return 0;
    15 }
    View Code

    三、威佐夫博奕(Wythoff Game):

          有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。、

    (此处先省略。。。。)

    四、尼姆博奕(Nimm Game):

       有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

      1.有两个玩家;

      2. 有三堆扑克牌(比如:可以分别是    5,7,9张);

      3. 游戏双方轮流操作;

      4. 玩家的每次操作是选择其中某一堆牌,然后从中取走任意张;

      5.最后一次取牌的一方为获胜方;

      对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示位异或(xor)运算。


      参考博文:http://www.cnitblog.com/weiweibbs/articles/42734.html

                http://www.cnblogs.com/kuangbin/archive/2011/08/28/2156426.html
          证明:http://acm.hdu.edu.cn/forum/read.php?fid=9&tid=10617

    任何奇异局势(a,b,c)(即必败态)都有a(+)b(+)c =0。

    如果我们面对的是一个非奇异局势(a,b,c),要如何变为奇异局势呢?假设 a < b< c,

    我们只要将 c 变为 a(+)b 即可,因为有如下的运算结果: a(+)b(+)(a(+)b)=(a(+)a)(+)(b(+)b)=0(+)0=0。

    例:(14,21,39),14(+)21=27,39-27=12,所以从39中拿走12个物体即可达到奇异局势(14,21,27)。

        我们来实际进行一盘比赛看看:

            甲:(7,8,9)->(1,8,9)奇异局势
            乙:(1,8,9)->(1,8,4)
            甲:(1,8,4)->(1,5,4)奇异局势
            乙:(1,5,4)->(1,4,4)
            甲:(1,4,4)->(0,4,4)奇异局势
            乙:(0,4,4)->(0,4,2)
            甲:(0.4,2)->(0,2,2)奇异局势
            乙:(0,2,2)->(0,2,1)
            甲:(0,2,1)->(0,1,1)奇异局势
            乙:(0,1,1)->(0,1,0)
            甲:(0,1,0)->(0,0,0)奇异局势
            甲胜。

       练习题:hdu  1849    1850

      hdu1849解题报告:

    Nim游戏模型:有三堆石子,分别含有a、b、c个石子。两人轮流从某一堆中取任意多的石子,规定每次至少取一个,多者不限。最后取光者得胜。

    定理1:Nim游戏的一个状态(a、b、c)是P状态,当且仅当a^b^c =0。

    对应此题就是:共有m个棋子就是m堆石子,把每个位置的标号等价于该堆石子的数目,取走最后一颗石子的人获胜,就是最后一个回到0位置的人获胜。即Nim博弈问题。

       AC代码如下:

     1 #include<stdio.h>
     2 int main()
     3 {
     4     int n,i,Nim[1010];
     5     while(scanf("%d",&n)&&n!=0)
     6     {
     7         int sg=0;
     8         for(i=0;i<n;i++)
     9         {
    10             scanf("%d",&Nim[i]);
    11             sg^=Nim[i];
    12         }
    13         if(sg==0)
    14             printf("Grass Win!
    ");
    15         else
    16             printf("Rabbit Win!
    ");
    17     }
    18     return 0;
    19 }
    View Code

    hdu  1850解题报告

    题意:有n堆石子,两个人轮流从n堆石子中取走至少1个石子问先手能否获胜,若能,输出先手可取的方案数

    解法:

    1. 本题中每一堆都可以选任意个,所以每一堆的SG值都是所剩余的个数。 
    2. 最后结果是所有堆的SG值异或的结果。令sg = 所有堆的SG值异或的结果 
    3. 如果sg == 0,则是必败点。 
    4. 如果sg != 0,使取后结果为0的策略是必胜策略 
    5. 具体怎么取呢? if(sg^a[i]<a[i])  ans++  即可,为什么呢?
    6. 有n堆物品,最多有n个情况,因为一次只能拿一堆的物品,所以在这一堆拿多少受限于其他n-1堆,所以就可以枚举所有的奇异局势,看目前这一堆需要多少个物品才能满足奇异局势,(上面这些是针对开始时候是非奇异局势来说的),然后求出来这个值再与之比较
    7. 每一堆的数值与sg相异或,所得的结果就是这一堆取走物品后剩余的数量,即(a,b,c)-->(a,b,a^b), 因为sg=a^b^c,所以sg^c=a^b^(c^c)=a^b^0=a^b,故sg^c就是非奇异局势时 c 堆上取走一定的石子后剩余的数量,明显这要比原始的数量少才可行,所以要满足条件:sg^a[i]<a[i],只要满足这个条件就是一种可行方案了

    AC代码如下:

     1 #include<stdio.h>
     2 int main()
     3 {
     4     int n,i,a[105];
     5     while(scanf("%d",&n)&&n)
     6     {
     7         int sg=0;
     8         for(i=0;i<n;i++)
     9         {
    10             scanf("%d",&a[i]);
    11             sg^=a[i];
    12         }
    13         int ans=0;
    14         for(i=0;i<n;i++)
    15             if((sg^a[i])<a[i])
    16                 ans++;
    17         printf("%d
    ",ans);
    18     }
    19     return 0;
    20 }
    View Code

    五、SG函数

     参考博文:http://www.cnitblog.com/weiweibbs/articles/42735.html

    SG即Sprague-Garundy函数,把博弈游戏抽象成有向无环图

    (1) 有向无环图
    (2) 玩家1先移动,起点是x0
    (3) 两个玩家轮流移动
    (4) 对于顶点x, 玩家能够移动到的顶点集记为F(x).
    (5) 不能移动的玩家会输掉游戏

    首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、 mex{2,3,5}=0、mex{}=0。

    定义: 一个图的Sprague-Grundy函数(X,F)是定义在X上的非负函数g(x),并且满足:
           g(x) = mex{g(y) : y∈F(x)}

    看到这里先好好理解一下sg值是怎么求的;

    如果在取子游戏中每次只能取{1,2,3},那么各个数的SG值是多少?

    x      0 1 2 3 4 5 6 7 8 9 10 11 12 13 14. . .
    g(x) 0 1 2 3 0 1 2 3 0 1  2   3   0   1   2. . .
    看看这个和上面那个图的规律:

      P-点: 即令 g(x) = 0 的 x 点!
     N-点: 即令 g(x) > 0 的 x 点!

    最后看下组合博弈,就是把简单的游戏组合起来,比如3堆的可以看成3个一堆的游戏。

     定理:

    假设游戏 Gi的SG函数是gi, i=1,…,n, 则
    G = G1 + … + Gn 的 SG函数是
    g(x1,…,xn) = g1(x1)⊕…⊕gn(xn).

    其中那个符合就是异或^

    看看是不是和Nim游戏的结论差不多?

    SG打表模板 :

     1 //f[]:可以取走的石子个数
     2 //sg[]:0~n的SG函数值
     3 //hash[]:mex{}
     4 int f[N],sg[N],hash[N];     
     5 void getSG(int n)
     6 {
     7     int i,j;
     8     memset(sg,0,sizeof(sg));
     9     for(i=1;i<=n;i++)
    10     {
    11         memset(hash,0,sizeof(hash));
    12         for(j=1;f[j]<=i;j++)
    13             hash[sg[i-f[j]]]=1;
    14         for(j=0;j<=n;j++)    //求mes{}中未出现的最小的非负整数
    15         {
    16             if(hash[j]==0)
    17             {
    18                 sg[i]=j;
    19                 break;
    20             }
    21         }
    22     }
    23 }
    View Code

    dfs模板:

     1 //注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍
     2 //n是集合s的大小 S[i]是定义的特殊取法规则的数组
     3 int s[110],sg[10010],n;
     4 int SG_dfs(int x)
     5 {
     6     int i;
     7     if(sg[x]!=-1)
     8         return sg[x];
     9     bool vis[110];
    10     memset(vis,0,sizeof(vis));
    11     for(i=0;i<n;i++)
    12     {
    13         if(x>=s[i])
    14         {
    15             SG_dfs(x-s[i]);
    16             vis[sg[x-s[i]]]=1;
    17         }
    18     }
    19     int e;
    20     for(i=0;;i++)
    21         if(!vis[i])
    22         {
    23             e=i;
    24             break;
    25         }
    26     return sg[x]=e;
    27 }
    View Code

    有关SG 函数的模板请参考:

    http://www.cnblogs.com/frog112111/p/3199780.html

    http://blog.csdn.net/y990041769/article/details/21406335

  • 相关阅读:
    C#获取Word文档页数,并跳转到指定的页面获取页面信息
    GC 垃圾回收
    Open Flash Chart 之线图
    Open Flash Chart 之线图(二)
    Nullable可空类型
    System.AppDomain类
    C# 事件
    C#方法的参数 Ref Out Params 4种类型的参数
    单向链表
    C# 结构体 struct
  • 原文地址:https://www.cnblogs.com/fightfordream/p/5743523.html
Copyright © 2011-2022 走看看