zoukankan      html  css  js  c++  java
  • 博弈论初识

    概念:

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

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

    定理:

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

         二、所有一步能走到必败点P的就是N点;

      三、通过一步操作只能到N点的就是P点;

    又即:只要当前状态可以转移到的状态中有一个是败态,那么当前状态就是胜态。如果当前状态可以转移到的所有状态都是胜态,那么当前状态就是败态。

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

    1,如果n=m+1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,

    后取者都能够一次拿走剩余的物品,后者取胜。必败

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

    那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。

    总之,要保持给对手留下(m+1)的倍数,就能最后获胜。必胜局

     

    Wythoff Game:有两堆各若干个物品,每个人每次可以从一堆里取任意多的物品,或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,轮流取,最后取光者得胜

     

     

     

    我们用(a[k]b[k])(a[k] b[k] ,k=012...,n)表示两堆物品的数量并称其为局势。

     

    如果甲面对(00),那么甲已经输了,这种局势我们称为奇异局势。

     

    首先列举人们已经发现的前几个奇异局势:(00)、(12)、(35)、(47)、(610)(813)、(915)、(1118)、(1220)。

     

    通过观察发现:a[0]=b[0]=0,a[k]是未在前面出现过的最小自然数,b[k]= a[k] + k

     

    4,奇异局势有如下三条性质:

     

      1)任何自然数都包含且仅包含在一个奇异局势中。

     

      2)任意操作都可以使奇异局势变为非奇异局势。

     

      3)必有一种操作可以使非奇异局势变为奇异局势。

     

    5,奇异局势公式:

     

    a[k]=[k*(1+5)/2]b[k]=a[k]+k

     

    (k=0,1,2......[ ]表示取整)

     

    有趣的是,式中的(1+5)/2正是黄金分割比例。

     

    6,判断

     

    可以看出,如果两人都采取正确的操作,那么对于非奇异局势,先拿者必胜,对于奇异局势,

     

    先拿者必败。

     

     

     1 #include <stdio.h>  
     2 
     3 #include <math.h>  
     4 
     5 const double Gsr=(1+sqrt(5.0))/2;  
     6 
     7 void swap(int &a,int &b)  
     8 
     9 {  
    10 
    11     int t=b;  
    12 
    13     b=a;  
    14 
    15     a=t;  
    16 
    17 }  
    18 
    19 int main()  
    20 
    21 {  
    22 
    23     int a,b;  
    24 
    25     while(~scanf("%d%d",&a,&b))  
    26 
    27     {  
    28 
    29         if(a>b)  
    30 
    31             swap(a,b);  
    32 
    33         if(a == (int)(Gsr*(b-a))) //奇异局势,先拿者输  
    34 
    35             puts("First Lose");  
    36 
    37         else  
    38 
    39             puts("First Win");  
    40 
    41     }  
    42 
    43     return 0;  
    44 
    45 }  

     

     

     

     

    Sprague-Grundy定理(SG定理):

    游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。而Bouton定理就是Sprague-Grundy定理在Nim游戏中的直接应用,因为单堆的Nim游戏 SG函数满足 SG(x) = x

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

    对于任意状态 x , 定义 SG(x) = mex(S),其中 S x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)} 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。

     

    如:6张扑克牌两名玩家轮流取,每次只能取1张或两张,谁先取完,谁先获胜。

    终结点(剩下0张牌)必定是必败点。

    剩下一张牌的时候,必胜;

    剩下两张牌的时候,必胜;

    剩下三张牌的时候,无论取一张或者两张,必败;

    剩下四张牌的时候,可以只取一张,让对方遇到只剩三张牌的情况;

    剩下五张牌的时候,可以只取两张,让对方遇到只剩三张牌的情况;

    剩下六张牌的时候,无论取一张还是两张,对方都可以让你遇到只剩三张牌的情况。所以先手必输。

    标记胜败点,则有

    0 1 2 3 4 5 6

    P N N P N N P

    发现是有规律的。

    记剩余x张牌为状态x,剩余0张牌时,SG[0]=mex{}=0;

    x=1,可以取1-f{1}张牌,剩余0个,SG[1]=mex{SG[0]}=mex{0}=1;

    x=2,可以取2-f{1,2}张牌,剩余1,0个,SG[2]=mex{SG[1],SG[0]}=mex{0,1}=2;

    x=3,可以取3-f{1,2}张牌,剩余2,1个,SG[3]=mex{SG[1],SG[2]}=mex{1,2}=0;

    x=4,可以取4-f{1,2}张牌,剩余3,2个,SG[1]=mex{SG[3]SG[2]}=mex{0,2}=1;

    x=5,可以取5-f{1,2}张牌,剩余4,3个,SG[1]=mex{SG[4],SG[3]}=mex{0,1}=2;

    x=6,可以取6-f{1,2}张牌,剩余5,4个,SG[1]=mex{SG[5],SG[4]}=mex{1,2}=0;

    可以列出

    0 1 2 3 4 5 6

    0 1 2 0 1 2 0

    由上述实例我们就可以得到SG函数值求解步骤,那么计算1~nSG函数值步骤如下:

    1、使用 数组f 将 可改变当前状态 的方式记录下来。

    2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。

    3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)

    4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值。

    SG[6]0则先手必输,否则先手必胜。

     

     

    N堆物品,每堆有M[i](1 <= i <= N)个物品,两个人轮流从任意一堆上取任意多的物品,最后取光者胜。两人都采取最优策略,问,是先手赢还是后手赢?

     

     

    定理(亦是结论):如果SG[M[1]] xor SG[M[2]] xor SG[M[3]] xor …… xor SG[M[N]] == 0,那么先手输,否则先手赢。(xor是位运算中的异或操作)

     1 //f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理 
     2 //SG[]:0~n的SG函数值 
     3 //S[]:为x后继状态的集合 
     4 int f[N],SG[MAXN],S[MAXN];
     5 void  getSG(int n){
     6     int i,j;     
     7     memset(SG,0,sizeof(SG));
     8     //因为SG[0]始终等于0,所以i从1开始 
     9     for(i = 1; i <= n; i++){
    10     //每一次都要将上一状态 的 后继集合 重置
    11       memset(S,0,sizeof(S));
    12       for(j = 0; f[j] <= i && j <= N; j++)
    13          S[SG[i-f[j]]] = 1;  //将后继状态的SG函数值进行标记
    14       for(j = 0;; j++) if(!S[j]){   //查询当前后继状态SG值中最小的非零值
    15            SG[i] = j;
    16            break;
    17       }
    18   }
    19}

    说了一大堆,也没懂多少。

    就当作记录一下都看了些啥好了。

  • 相关阅读:
    Mybatis3详解(一)----Mybatis的介绍
    【k8s】svc-sessionAffinityConfig
    【k8s】svc-sessionAffinity
    【k8s】svc-selector
    【k8s】svc-publishNotReadyAddresses
    【k8s】svc-ports
    【k8s】svc-externalIPs
    【k8s】svc-clusterIPs
    【k8s】svc-clusterIP
    【k8s】Service
  • 原文地址:https://www.cnblogs.com/Jiiiin/p/8680807.html
Copyright © 2011-2022 走看看