取石子游戏之三种博弈总结

 

取石子游戏之三种博弈总结

576人阅读  评论(0)  收藏  举报
  分类:
博弈(2) 

转载:http://blog.csdn.net/xuzengqiang/article/details/7773893

1、取石子游戏之巴什博弈

下面这段来自白白の屋的文章的一段:

巴什博弈:只有一堆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)的倍数,就能最后获胜。那么这个时候只要n%(m+1)!=0,先取者一定获胜

    这个游戏还可以有一种变相的玩法:两个人轮流报数,每次至少报一个,最多报十个,谁能报到100者胜。

    分析此类问题主要放法是:P/N分析:

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

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

     三个定理:

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

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

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

巴什博弈的一个最重要的特征就是只有一堆。然后就在其中改,要么在范围内不规定个数,要么就规定只能取几个,再要么就倒过来,毕竟是最简单的博弈,代码相对而言较短额~

例题1:NYOJ 23(取石子游戏一),巴什博弈,只要n%(m+1)!=0,则先取者一定获胜。

[cpp]  view plain copy
  1. #include<iostream>  
  2. using namespace std;   
  3. int main()  
  4. {   int n,m,Case;  
  5.     cin>>Case;  
  6.     while(Case--)  
  7.     {   cin>>n>>m;  
  8.         cout<<(n%(m+1)?"Win":"Lose")<<endl;  
  9.     }  
  10.     return 0;  
  11. }  

例题2:HDU 2149,英语标题额,原来是中文题。反过来就是一样的啦,只是取数时的规律是:如果M%(N+1)!=0,那么第一个取的数就是M%(N+1),留给对手的是(N+1)的倍数,还有就是M<N的情况,不说你也懂,开始写了sort的,但后来一看可以不要,代码:

[cpp]  view plain copy
  1. #include<iostream>  
  2. using namespace std;  
  3. int main()  
  4. {   int n,m;  
  5.     while(scanf("%d%d",&m,&n)!=EOF)  
  6.     {   int sum=0;  
  7.         if(m%(n+1)==0) cout<<"none";  
  8.         else  
  9.         {   if(m%(n+1))  cout<<m%(n+1); //直接取余数   
  10.             if(n>=m)  
  11.             {   for(int i=m+1;i<=n;i++)  
  12.                     cout<<" "<<i;  
  13.             }   
  14.         }  
  15.         cout<<endl;  
  16.     }  
  17.     return 0;  
  18. }  

题3:HDU 1847,寻找必败态,n%3=0则Cici赢,否则Kiki赢。

分析:(1)、若是留给Cici的是3,那么Cici只能取1个或2个,所以再轮到Kiki取的话必赢。

            (2)、若是给Cici留下的是3的倍数,假设为3n(n=1,2,3,..),那么无论Cici取什么数,剩余的数一定可以写成3m+1或者3m+2(m<n)的形式,那么只要Kiki再取的时候留给Cici的仍然是3的倍数的话,就必胜了。代码略。

当然这种题目可以直接先枚举前面几个数,就能找到规律啦~
 


2、取石子游戏之威佐夫博弈

下面博文中有一部分是转载自ACM and JAVA的文章。

威佐夫博弈:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
我们用(ak,bk)(ak<=bk,k=0,1,2,3...n)表示两堆物品的数量,并将这种形式记为局势。如果甲面对局势(0,0),说明甲输了,我们把这种情况叫做奇异局势

现在的问题我给你一个局势(a,b),我们怎么判断它是不是奇异局势呢?

判断公式为:ak=[k(1+√5)/2],bk=ak+k(k=0,1,2,…,n,[]:取整-高斯函数).可以令:k=b-a;那么如果:a=[(b-a)(1+√5)/2],那么这个局势为奇异局势。

如果面对非奇异局势,先拿者必胜;反之,则后拿者取胜

例题:POJ 1067,直接用公式即可,下面为模版:

[cpp]  view plain copy
  1. #include<iostream>  
  2. #include<cmath>  
  3. using namespace std;  
  4. #define S sqrt(5.0)  
  5. int main()  
  6. {   int a,b;  
  7.     while(cin>>a>>b)  
  8.     {     
  9.         if(a>b) swap(a,b);  
  10.         if(a==int((b-a)*(S+1)/2)) cout<<0<<endl;  
  11.         else cout<<1<<endl; //非奇异局势,先拿着必胜   
  12.     }  
  13.     return 0;  
  14. }  

我们知道a0=b0=0,ak是没有在前面出现过的最小自然数,bk=ak+k,由上面的代码可以知道前几组的奇异局势:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,1)、(9,15)、(11,18)、(12,20)。

奇异局势的三个性质为:

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

原理:ak是没有在前面出现过的最小自然数,所以ak>ak-1,bk=ak+k>ak-1+(k-1)=bk-1>ak-1。

(2)、任意操作都可将奇异局势变为非奇异局势

原理:若是只改变奇异局势(ak,bk)中的一个分量,由于另一个分量不可能存在于其他的奇异局势中,所以改变后一定是非奇异局势,如果使(ak,bk)的两个分量同时减少,则由于其差不变,且不可能是其他奇异局势的差,因此也是非奇异局势。

(3)、采用适当的方法,可以将非奇异局势变为奇异局势

必败态的公式求解过程,需用到贝亚蒂定理

若p,q∈R+(正实数集),且p,q为无理数,使得1/p+1/q=1,定义集合(贝亚蒂序列)P={ [np]:n∈Z+ },Q={ [nq]:n∈Z+ },则P 和Q 构成正整数集的一个分划:P∩Q=空集,PUQ=Z+,

也就是说:如果两个正无理数的倒数之和是1,则任何正整数都可刚好以一种形式表示为不大于其中一个无理数的正整数倍的最大整数

由性质1知:ak,bk刚好为贝亚蒂序列, 那么我可以令ak=[kp],bk=[kq],且1/p+1/q=1。由于bk=ak+k=[kp]+k=[k(p+1)]=[kq],所以q=p+1,则有:1/p+1/(p+1)=1,可以求出p=(√5+1)/2。这就是必败态的通向公式:ak=[k(1+√5)/2]。

下面是一张必败态的表,很Nice的~


3、取石子游戏之尼姆博弈

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

这种情况与二进制有着很大的关系,我们用(a,b,c)来表示某种局势,那么(0,0,0)必然为奇异局势,最后一个面对这个局势的必败。(0,n,n)也是种奇异局势。因为如果对手在其中一堆取m个石子(m<=n),那么你也可以在另外一堆中取m个,他取几个你就取几个,到最后有一堆变为0的时候,你再取完另一堆胜利。

直接说结论吧:

对于任意的奇异局势(a,b,c),都有a^b^c=0。(^为异或运算)

对于任意的非奇异局势(a,b,c),假设a<b<c,将它变为奇异局势的方法是:将c变成a^b。

原理:因为a^a=0,所以a^b^c=a^b^(a^b)=(a^a)^(b^b)=0,所以只需将c-(a^b)即可。

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

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

性质1:对于某个局面(a1,a2,...,an),若a1^a2^...^an!=0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。

分析:令res=a1^a2^......an,设res的最高位为pos(那pos的数肯定为1啦),那么一定存在某个ai,它的二进制在pos位上也是1,(否则k的最高位那个1是怎么得到的)。异或res后这位变为0,这时ai^res<ai一定成立。则我们可以将ai改变成ai'=ai^res,此时a1^a2^...^ai'^...^an=a1^a2^...^an^res=0。

性质2:对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后仍满足a1^a2^...^ai'^...^an=0。

例题1:POJ 2975,题目意思是给你一组局势,问有多少中必胜的策略。

分析:求出res=a[0]^a[1]^a[2].....a[n-1],由于每次只能改变一堆石子的数量,由性质1,可知如果res!=0,那么一定存在某个合法的操作,使得res=0,只要满足a[i]*res<=a[i]。

[cpp]  view plain copy
  1. #include<iostream>  
  2. using namespace std;  
  3. const int MAX=1010;  
  4. int a[MAX];   
  5. int main()  
  6. {   int n,res;  
  7.     while(cin>>n,n)  
  8.     {   int count=0;  
  9.         for(int i=0;i<n;i++)  
  10.             cin>>a[i];      
  11.         res=a[0];  
  12.         for(int i=1;i<n;i++)  
  13.             res^=a[i];  
  14.         if(res==0) cout<<0<<endl;  
  15.         else  
  16.         {   for(int i=0;i<n;i++)  
  17.                if((a[i]^res)<a[i])  count++; //要注意^和<=的优先级,必须加括号   
  18.             cout<<count<<endl;  
  19.         }   
  20.     }  
  21.     return 0;  
  22. }  

SG函数:定义mex运算为最小不属于这个集合的非负整数。mex{0,1,2,4}=3,mex{}=0。

对于一个给定的有向无环图,我们定义关于图的每个顶点的SG函数g如下:g(x)=mex{g(y) | y是x的后继}

SG函数的性质:由于是一个有向无环图,那么对于所有的末端位置,由于没有后继,所以SG=0。另外对于一个g(x)=0的顶点x,它的所有后继y都满足g(y)!=0(由于集合中的元素是不可以重复的,所以后面的g(y)!=0)。对于一个g(x)!=0的顶点,必定存在一个后继y满足g(y)=0(这个时候最小的非负整数一定是0)。

由上可知:顶点x所代表的位置是必败点条件是:g(x)=0

看到一个经典的话额,就贴在这里吧:

有些事,明知是错的,也要去坚持,因为不甘心;有些人,明知是爱的,也要去放弃,因为没结局;有时候,明知没路了,却还在前行,因为习惯了。
 

猜你喜欢

转载自blog.csdn.net/jianxingzhang/article/details/79900247
今日推荐