几种博弈问题及算法

巴什博弈

巴什博弈是最基础的博弈游戏。

 有一堆石子,共计n颗,规定二人每次拿1~m颗石头,先拿完者胜,求解先手是否能赢。

算法思想非常好考虑:

因为每次最多拿m颗石头,所以当n是m+1的倍数时,first无论拿多少颗,second只要在first之后拿能够凑够m+1颗石子就能胜利,所以first必败。

如果n不是m+1的倍数,假设n = (m+1)*s + r(1 <= r <= m-1),那么first只要在首轮拿走r颗石子,使得此时石子数目为m+1的倍数,就将必败态转移给了second,first延续上述的策略,first必胜。

参考题目:

HDU1849 

#include <cstdio>
#include <iostream>
using namespace std;
int main()
{
    int N;
    cin>>N;
    while(N--)
    {
            int n,m;
    scanf("%d %d",&n,&m);
        if(n %(m+1) == 0)
        {
            cout<<"second"<<endl;
        }
        else
        {
            cout<<"first"<<endl;
        }
    }

}

HDU2149

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 1005;

int main()
{
    int m,n;
    //田地m
    while(scanf("%d %d",&m,&n) != EOF)
    {
        if(m<=n)
        {
            for(int i = m; i<n; i++)
                cout<<i<<" ";
            cout<<n<<endl;
        }
        else
        {
            int r = m%(n+1);
            if(r == 0)
                cout<<"none"<<endl;
            else
                cout<<r<<endl;
        }
    }
}

威佐夫博弈

有两堆石子,两个玩家可以(1)从两堆石子中分别取相同数目的石子(2)从一堆石子中取任意数目的石子,求解先手是否能赢。

扫描二维码关注公众号,回复: 3037940 查看本文章

算法思想很巧(而且数学思想很BT):

首先我们引入奇异局势(也就是必败局势,我实在是很不明白,为什么好好的名词非要按字面翻译成各种令人费解的名字)的概念:可以知道,(0,0)一定是一个奇异局势,以此类推(0,i),(i,i),(i,0)就一定是必胜局势。因必胜局势的上一步便是奇异局势,故我们可以得到奇异局势如下:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)……

可以发现(我没发现),奇异局势(ak,bk)是这样构成的:(ak,ak+k),ak是在前面未出现过的最小自然数。

奇异局势有以下三条性质:

  1. 任何自然数都包含在唯一一个奇异局势之中。
  2. 任意操作都可将奇异局势变为非奇异局势。
  3. 方法适当,非奇异局势可以变为奇异局势。

假设面对的局势是(a,b),若 b = a,则同时从两堆中取走 a 个物体,就变为了奇异局势(0,0);

如果a = ak ,b > bk,那么,取走b  – bk个物体,即变为奇异局势;

如果 a = ak ,  b < bk ,则同时从两堆中拿走 ak – ab + ak个物体,变为奇异局势( ab – ak , ab – ak+ b – ak);

如果a > ak ,b= ak + k,则从第一堆中拿走多余的数量a – ak 即可;如果a < ak ,b= ak + k,分两种情况,第一种,a=aj (j < k),从第二堆里面拿走 b – bj 即可;第二种,a=bj (j < k),从第二堆里面拿走 b – aj 即可。

以上关于非奇异到奇异的拿法是我粘的,总之,判断(a,b)是否为奇异矩阵的公式如下:(n-m)*\frac{1+\sqrt{5}}{2} == m,如果成立,则此局面为奇异局势,先手必败。

参考题目:

POJ1067

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
    long long n,m;
    while(scanf("%I64d %I64d",&n,&m) != EOF)
    {
        if(n<m)
        {
            int t = m;
            m = n;
            n = t;
        }
        double w=(sqrt(5)+1)/2;
        long long s = (n-m)*w;
        if(s == m)
            printf("0\n");
        else
            printf("1\n");
    }
}

尼姆博弈

尼姆博弈可以说是巴什博弈的加强版,即有N堆石子,可以从一堆石子中拿走任意个,先拿完者胜

算法推导可以见这位博主写的博文POJ-2234

一个格局记为(x1,x2,...,xn),且次序无关,此格局为 P-格局 当且仅当 x1^x2^...^xn = 0.其中^是按位异或运算

参考题目:

POJ2234

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn =1005;
const int INF  =1e9;

int main()
{
    int n;
    while(scanf("%d",&n) != EOF)
    {
        int a[maxn];
        int ans = 0;
        for(int i = 1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            ans ^= a[i];
        }
        if(ans) printf("Yes\n");
        else printf("No\n");

    }
    return 0;
}

HDU1850

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 105;

int main()
{
    int n;
    while(scanf("%d",&n) != EOF && n)
    {
        int a[maxn];
        int tmp = 0;
        int sum = 0;
        for(int i = 1; i<=n; i++)
        {
            cin>>a[i];
            tmp ^= a[i];
        }
        if(tmp == 0)
            cout<<"0"<<endl;
        else
        {
            for(int i = 1; i<=n; i++)
            {
                if(a[i]>(tmp^a[i]))
                    sum++;
            }
            cout<<sum<<endl;
        }

    }
}

斐波那契博弈

1堆石子有n个(n>1),两人轮流取.先取者第1次可以取任意多个,但不能全部取完.以后每次取的石子数不能超过上次取子数的2倍,先取完者胜。

算法如下:

判断石子数是否为斐波那契数即可,若n = f(k),则先手必败。

因为在斐波那契数列中,f(i) = f(i - 1) + f (i - 2),所以很好证明f(i) <=  f(i-1)*2.

参考题目

HDU2516

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
    long long fib[120];
    fib[1] = 1;
    fib[2] = 1;
    for(int i = 3; i<120; i++)
        fib[i] = fib[i-1] + fib[i-2];
    int n;
    while(scanf("%d",&n) != EOF)
    {
        if(n==0)break;
        int flag = 0;
        for(int i = 1; i<120; i++)
        {
            if(fib[i] == n)
            {
                flag = 1;
                break;
            }
        }
        if(flag)
            cout<<"Second win"<<endl;
        else
            cout<<"First win"<<endl;
    }
}

环形博弈

n个石子围成一个环,每次可取走1个或相邻的两个,注意若两个石子之间的石子被取走,这两个石子仍然是不相邻的。

算法比较精妙,值得借鉴

对于n=1和n=2的情况,先手必胜。

当n=3时,显然先手必败。

当n>3时,我们可以对这个环画一个对称轴,无论先手怎么取,后手只要让这个环呈对称状就能赢。

参考题目:

POJ2484

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 1005;

int main()
{
    int n;
    while(scanf("%d",&n)!= EOF &&n)
    {
        if(n==1||n==2)
            cout<<"Alice"<<endl;
        else
            cout<<"Bob"<<endl;
    }
}

HDU3951

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn =1005;
const int INF  =1e9;

int main()
{
    int N;
    while(scanf("%d",&N) != EOF)
    {
        for(int t = 1; t<= N; t++)
        {
            int n,k;
            cin>>n>>k;
            if(k == 1)
            {
                if(n%2)
                    printf("Case %d: first\n",t);
                else
                    printf("Case %d: second\n",t);
            }
            else
            {
                if(n>k)
                    printf("Case %d: second\n",t);
                else
                    printf("Case %d: first\n",t);
            }
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_41934068/article/details/81673696