zoukankan      html  css  js  c++  java
  • 组合博弈入门

     以下为网上搜集资料的汇总:

    组合游戏定义

           1、有且仅有两个玩家    2、游戏双方轮流操作    3、游戏操作状态是个有限的集合(比如:取石子游戏,石子是有限的,棋盘中的棋盘大小的有限的)  4、游戏必须在有限次内结束  5、当一方无法操作时,游戏结束。

    (一)巴什博奕(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)的倍数,就能最后获胜。

    练习:hdu1846 Brave Game 

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

    (二)威佐夫博奕(Wythoff Game):有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

        这种情况下是颇为复杂的。我们用(a[k],b[k])(a[k]≤b[k],k=0,1,2,…,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。

        可以看出,a[0]=b[0]=0,a[k]是未在前面出现过的最小自然数,而 b[k]=a[k]+k,奇异局势有如下三条性质

        1。任何自然数都包含在一个且仅有一个奇异局势中。
        由于a[k]是未在前面出现过的最小自然数,所以有a[k]>a[k-1],而b[k]=a[k]+k>a[k-1]+ k-1 = b[k-1]>a[k-1]。所以性质1。成立。
        2。任意操作都可将奇异局势变为非奇异局势。
        事实上,若只改变奇异局势(a[k],b[k])的某一个分量,那么另一个分量不可能在其他奇异局势中,所以必然是非奇异局势。如果使(a[k],b[k])的两个分量同时减少,则由于其差不变,且不可能是其他奇异局势的差,因此也是非奇异局势。
        3。采用适当的方法,可以将非奇异局势变为奇异局势。

        假设面对的局势是(a,b),若 b=a,则同时从两堆中取走 a 个物体,就变为了奇异局势(0,0);如果a = a[k] ,b > b[k],那么,取走b–b[k]个物体,即变为奇异局势;如果 a = a[k] ,b < b[k] ,则同时从两堆中拿走 a–a[b–a]个物体,变为奇异局势( a[b-a] , a[b–a] + b-a);如果a > a[k] ,b= a[k] + k,则从第一堆中拿走多余的数量a–a[k] 即可;如果a < a[k] ,b= a[k] + k,分两种情况,第一种,a=a[j] (j < k),从第二堆里面拿走 b-b[j] 即可;第二种,a=b[j] (j < k),从第二堆里面拿走 b–a[j] 即可。

        从如上性质可知,两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。

        那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:a[k] =[k(1+√5)/2],b[k]= a[k] + k (k=0,1,2,…,n 方括号表示取整函数)奇妙的是其中出现了黄金分割数(1+√5)/2 = 1。618…,因此,由a[k],b[k]组成的矩形近似为黄金矩形,由于2/(1+√5)=(√5-1)/2,可以先求出j=[a(√5-1)/2],若a=[j(1+√5)/2],那么a = a[j],b[j] = a[j] + j,若不等于,那么a = a[j]+1,b = a[j]+1+ j+1,若都不是,那么就不是奇异局势。然后再按照上述法则进行,一定会遇到奇异局势。

    练习:poj1067 取石子游戏

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cmath>
     4 using namespace std;
     5 int main(){
     6     int a,b,k,ak;
     7     while(scanf("%d%d",&a,&b)==2){
     8         k=abs(b-a);
     9         a=min(a,b);
    10         ak=floor(k*(1+sqrt(5.0))/2);
    11         printf("%d
    ",ak!=a);
    12     }
    13     return 0;
    14 }
    Code

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

        这种情况最有意思,它与二进制有密切关系,我们用(a,b,c)表示某种局势,首先(0,0,0)显然是奇异局势,无论谁面对奇异局势,都必然失败。第二种奇异局势是(0,n,n),只要与对手拿走一样多的物品,最后都将导致(0,0,0)。仔细分析一下,(1,2,3)也是奇异局势,无论对手如何拿,接下来都可以变为(0,n,n)的情形。

        计算机算法里面有一种叫做按位模2加,也叫做异或的运算,我们用符号(+)表示这种运算。这种运算和一般加法不同的一点是1+1=0。先看(1,2,3)的按位模2加的结
    果:

    1 =二进制01
    2 =二进制10
    3 =二进制11 (+)
    ———————
    0 =二进制00 (注意不进位)

        对于奇异局势(0,n,n)也一样,结果也是0。

        任何奇异局势(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。

    要将c 变为a(+)b,只要从 c中减去 c-(a(+)b)即可

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

        例2。(55,81,121),55(+)81=102,121-102=19,所以从121中拿走19个物品就形成了奇异局势(55,81,102)。

        例3。(29,45,58),29(+)45=48,58-48=10,从58中拿走10个,变为(29,45,48)。

        例4。我们来实际进行一盘比赛看看:
            甲:(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)奇异局势
            甲胜。

    练习:poj2234 Matches Game

     1 #include<cstdio>
     2 int main(){
     3     int m,ans,x;
     4     while(scanf("%d",&m)==1){
     5         ans=0;
     6         while(m--){
     7             scanf("%d",&x);
     8             ans^=x;
     9         }
    10         if(ans) printf("Yes
    ");
    11         else printf("No
    ");
    12     }
    13 }
    Code

    poj2975 Nim

     1 #include<cstdio>
     2 const int N=1001;
     3 int n,a[N];
     4 int main(){
     5     int i,s,ans;
     6     while(scanf("%d",&n),n){
     7         for(s=i=0;i<n;++i){
     8             scanf("%d",&a[i]);
     9             s^=a[i];
    10         }
    11         for(ans=i=0;i<n;++i)
    12             if((s^a[i])<a[i])
    13             ans++;
    14         printf("%d
    ",ans);
    15     }
    16     return 0;
    17 }
    Code

    必胜点与必败点:

    必败点(P点) :上一次move的人有必胜策略的局面(先手必败)

    必胜点(N点) :轮到现在move的人有必胜策略的局面(先手必胜)

    性质:
    ① 所有终结点是必败点(P点);
    ②从任何必胜点(N点)操作,至少有一种方法可以进入必败点(P点);
    ③无论如何操作, 从必败点(P点)都只能进入必胜点(N点).

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

    思考:如果把Nim的规则略加改变,你还能很快找出必胜策略吗?比如说:有n堆石子,每次可以从第1堆石子里取1颗、2颗或3颗,可以从第2堆石子里取奇数颗,可以从第3堆及以后石子里取任意颗……

    Sprague-Grundy函数:

    给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。事实上,这个游戏可以认为是所有Impartial Combinatorial Games的抽象模型。也就是说,任何一个ICG都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。。。

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

    实例:取石子问题

    有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?

    SG[0]=0,f[]={1,3,4},

    x=1 时,可以取走1 - f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] }= mex{0} = 1;

    x=2 时,可以取走2 - f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] }= mex{1} = 0;

    x=3 时,可以取走3 - f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} =1;

    x=4 时,可以取走4-  f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2;

    x=5 时,可以取走5 - f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3;

    以此类推.....

       x     0  1  2  3  4  5  6  7  8....

    SG[x]    0  1  0  1  2  3  2  0  1....

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

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

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

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

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

    回顾上面的思考:我们可以把它看作3个子游戏,第1个子游戏只有一堆石子,每次可以取1、2、3颗,很容易看出x颗石子的局面的SG值是x%4。第2个子游戏也是只有一堆石子,每次可以取奇数颗,经过简单的画图可以知道这个游戏有x颗石子时的SG值是x%2。第3个游戏有n-2堆石子,就是一个Nim游戏。对于原游戏的每个局面,把三个子游戏的SG值异或一下就得到了整个游戏的SG值,然后就可以根据这个SG值判断是否有必胜策略以及做出决策了。其实看作3个子游戏还是保守了些,干脆看作n个子游戏,其中第1、2个子游戏如上所述,第3个及以后的子游戏都是“1堆石子,每次取几颗都可以”,称为“任取石子游戏”,这个超简单的游戏有x颗石子的SG值显然就是x,其实,n堆石子的Nim游戏本身就是n个“任取石子游戏”。

    SG定理:游戏的和的SG是它的所有子游戏的SG值的异或

    综上所述,SG函数与“游戏的和”的概念不是让我们去组合、制造稀奇古怪的游戏,而是把遇到的看上去有些复杂的游戏试图分成若干个子游戏,对于每个比原游戏简化很多的子游戏找出它的SG函数,然后全部异或起来就得到了原游戏的SG函数,就可以解决原游戏了。(“分而治之”的思想)

  • 相关阅读:
    一种针对SOA的消息类型架构
    许可方式 到底"非商业用途"意味着什么?
    Windows 7的CMD中 Telnet 无法执行的解决办法
    ASP.NET MVC 2.0 中文正式版发布
    什么是REST?
    架构、框架的区别
    Firefox 火狐下自动刷新的插件 ReloadEvery
    ASP.NET与JQUERY的AJAX文件上传 视频课件+源码Demo
    给吸烟的园友们:一个被烟草行业隐瞒了十年的秘密,烟真不是人吸的
    Echo Server,AsyncSocket,SocketAsyncEvent,SocketAsyncEventArgs,AsyncQueue
  • 原文地址:https://www.cnblogs.com/GraceSkyer/p/5719958.html
Copyright © 2011-2022 走看看