博弈

以下为百度百科的介绍(我把他们概括了一下):

1) 巴什博奕(Bash Game):

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

2) 威佐夫博奕(Wythoff Game):

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

3) 尼姆博奕(Nimm Game):

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

4) Nim Staircase博奕:

这个问题是尼姆博弈的拓展:游戏开始时有许多硬币任意分布在楼梯上,共n阶楼梯从地面由下向上编号为0到n。游戏者在每次操作时可以将楼梯j(1<=j<=n)上的任意多但至少一个硬币移动到楼梯j-1上。游戏者轮流操作,将最后一枚硬币移至地上(0号)的人获胜。

 

下面来说怎么做:

1)Bash

首先是一个逆向的推理,n处胜,(n-1)、(n-2)...(n-m)处败,(n-m-1)处胜利...,于是问题转化为何时得到n、(n-m-1)...这些必胜点,只需每次走到这个点,让对方改变这个状态,则必胜。我们发现,每两个必胜点相差(m+1),所以若n mod (m+1)=0,则先手需要改变这个必胜状态,所以先手必败;反之若n mod (m+1)≠0,则先手可以立刻走到必胜点,所以先手必胜。

 

2)Wythoff

 

3)Nimm

这个推导比较麻烦,那就说一下结论吧。

定义sum为所有物品数量的异或,若sum==0则称此状态为奇异状态,记T,反之为非奇异状态,记S。若存在一个物品数量大于1,记2,反之所有物品数量为1,记0。

先手必胜条件:S2,T0.

反之为先手必败。

而且如果改变题意为取最后一个为输,只需反一下最后结果就可以了。

 

#include <cstdio>
int n,sum,temp;
bool bo;
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        sum=0; bo=false;
        for(int i=1;i<=n;++i)
        {
            scanf("%d",&temp);
            sum^=temp;
            if(temp>1) bo=true;
        }
        if((sum==0&&bo==false)||(sum!=0&&bo==true)) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

 

  

 

  

 

4)Nim Staircase

这个博弈很神奇,可以通过一些转化,使其变成Nimm博弈。下面介绍这种转化。

先考虑偶数的台阶,当A移动一定的硬币到下一个奇数台阶,B可以以移动同样数量的硬币到再下一个偶数台阶。此时,奇数台阶的硬币是没有改变的。若A一直移动偶数台阶的硬币,B也可以一直移动,直到移到0,B必定取最后一个硬币。

于是发现,偶数台阶不影响胜负,那么下面考虑奇数台阶。由于偶数不影响胜负,那么我们把所有偶数台阶看作地面,奇数台阶的硬币移动到下一层可以看作移到地面。也就是取走石子类似,那么问题就转化为了一个经典的Nimm博弈。问题就可以迎刃而解了。

 

 

介绍完以上的博弈基础,我们还会遇到一种组合博弈,这是需要一个很强大的工具:SG函数。

先定义几样东西:

1) mex(minimal excludant):表示最小的不属于这个集合的非负整数

 

2) f(x):对于顶点x, 玩家能够移动到x的点集记为f(x)

 

3) sg(x) = mex{sg(y) | y∈f(x)}

 

对于sg函数的计算,我们可以从前往后一次得到。

 

定理1:sg的值不会超过mex的集合中数量的最大值。

 

对于一个博弈论游戏,我们可以把他分解为几个单独的博弈论基础问题,求出sg函数值,最后对不同基础问题的所有sg函数值求异或,即可得到整个游戏的sg函数值。

hdu1848是一道模板题。

 

#include <cstdio>
#include <cstring>
#define FOR(i,x,y) for(int i=x;i<=y;++i)
#define rFOR(i,x,y) for(int i=x;i>=y;--i)
using namespace std;
int n,m,p,sum,sg[1010],f[20]={1,1};
bool vis[20];
int mex(int a){
    int temp;
    memset(vis,0,sizeof(vis));//初始化
    rFOR(i,15,1)//逆着推
    {
        temp=a-f[i];
        if(temp>=0) vis[sg[temp]]=true;//删去所有可以走到的点
    }
    FOR(i,0,16) if(!vis[i]) return i;//sg可能的最大值也就是f[i]的不同值的数量。
}
int main(){
    FOR(i,2,15) f[i]=f[i-1]+f[i-2];//能够移动到的点集
    FOR(i,1,1000) sg[i]=mex(i);//sg函数计算
    while(scanf("%d%d%d",&n,&m,&p))
    {
        if(n==0&&m==0&&p==0) break;
        sum=0;
        sum=sg[n]^sg[m]^sg[p];
        if(sum==0) printf("Nacci\n");
        else printf("Fibo\n");
    }
    return 0;
}

 

  

 

  

 

 

你可能感兴趣的:(博弈)