码迷,mamicode.com
首页 > 其他好文 > 详细

51nod Bash游戏(V1,V2,V3,V4(斐波那契博弈))

时间:2015-08-21 23:22:23      阅读:430      评论:0      收藏:0      [点我收藏+]

标签:acm   algorithm   博弈   算法   编程   


Bash游戏V1


有一堆石子共有N个。A B两个人轮流拿,A先拿。每次最少拿1颗,最多拿K颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N和K,问最后谁能赢得比赛。
例如N = 3,K = 2。无论A如何拿,B都可以拿到最后1颗石子。
Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000)
第2 - T + 1行:每行2个数N,K。中间用空格分隔。(1 <= N,K <= 10^9)
Output
共T行,如果A获胜输出A,如果B获胜输出B。
Input示例
4
3 2
4 2
7 3
8 3
Output示例
B
A
A
B

解题思路:
    如果是(k+1)的整数倍,先手取任何一个1~k内的数x,后手都可以取(k+1-x)个石子,于是k+1的整数倍是先手的必败态,同理,不是k+1的整数倍时,先手可以取n%(k+1)个石子,从而使后手必败。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int main()
{
    int n,k,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&k);
        if(n%(k+1))
        printf("A\n");
        else
        printf("B\n");
    }
    return 0;
}


有一堆石子共有N个。A B两个人轮流拿,A先拿。每次只能拿1,3,4颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。
例如N = 2。A只能拿1颗,所以B可以拿到最后1颗石子。
Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000)
第2 - T + 1行:每行1个数N。(1 <= N <= 10^9)
Output
共T行,如果A获胜输出A,如果B获胜输出B。
Input示例
3
2
3
4
Output示例
B
A
A


解题思路:
    这题如果直接想不出来可以用sg函数打表发现规律来做。
 sg函数和sg定理:
     对于任意状态x,定义SG(x)=mex(S),其中S是x的后继状态的SG函数的集合,mex(s)表示不在S内的最小负整数。比如,x有6个后继状态,SG函数数值分别为0,1,1,2,4,7,则SG(x)=3,因为3是第一个没有出现在后继状态SG函数值集合中的非负整数。
这题的sg函数代码:
const int maxn=45;
bool vis[maxn];
int sg[maxn];
int a[5]={1,3,4};
void sgs()
{
    for(int i=0;i<maxn;i++)
    {
        memset(vis,false,sizeof(vis));
        for(int j=0;j<3;j++)
        {
            if(i>=a[j])
            vis[sg[i-a[j]]]=true;
        }
        for(int j=0;;j++)
        {
            if(!vis[j])
            {
                sg[i]=j;
                break;
            }
        }
        printf("%d %d\n",i,sg[i]);
    }
}

通过打表发现,7的整数倍和n%7==2的是必败态。

证明:
    1,对于2,肯定必败。
    2,对于1,3,4,先手必胜。
    3,对于5,6,先手可以取3,4,让后手进入必败态2。
    2,对于7,无论先手取什么,后手都可以让其进入必败态,先手取1,后手取4,进入必败态2;先        手取3,后手取4;先手取4,后手取3即可。于是只要是7的倍数或%7余2的,都是必败态,其他      都为必胜态。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        if(n%7==0||n%7==2)
        printf("B\n");
        else
        printf("A\n");
    }
    return 0;
}


有一堆石子共有N个。A B两个人轮流拿,A先拿。每次拿的数量只能是2的正整数次幂,比如(1,2,4,8,16....),拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。
例如N = 3。A只能拿1颗或2颗,所以B可以拿到最后1颗石子。(输入的N可能为大数)
Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 1000)
第2 - T + 1行:每行1个数N。(1 <= N <= 10^1000)
Output
共T行,如果A获胜输出A,如果B获胜输出B。
Input示例
3
2
3
4
Output示例
A
B
A


解题思路:
   sg函数:
const int maxn=1000+100;
int sg[maxn];
bool vis[maxn];
int main()
{
    for(int i=0;i<50;i++)
    {
        memset(vis,false,sizeof(vis));
        for(int j=0;(1<<j)<=i;j++)
        {
            int s=i-(1<<j);
            vis[sg[s]]=true;
        }
        for(int j=0;;j++)
        {
            if(!vis[j])
            {
                sg[i]=j;
                break;
            }
        }
        printf("%d ",sg[i]);
    }
    return 0;
}

发现只要是3的整数倍就可以。

证明:
   任意1个3的整数倍都可以转化为2*n个2的正整数幂的和。通过枚举可以发现:
   (2^0)%3=1; (2^1)%3=2;(2^2)%3=1;(2^3)%3=2;
   总是1和2,任意1个%3为1的加上%3为2的就可以组成%为3的数了。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
char s[maxn];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s",s);
        int n=strlen(s);
        int ans=0;
        for(int i=0;i<n;i++)
        {
            ans=ans+(s[i]-'0');
        }
        if(ans%3)
        printf("A\n");
        else
        printf("B\n");
    }
    return 0;
}

Bash游戏V4(斐波那契博弈)

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次拿的数量最少1个,最多不超过对手上一次拿的数量的2倍(A第1次拿时要求不能全拿走)。拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。
例如N = 3。A只能拿1颗或2颗,所以B可以拿到最后1颗石子。
Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 1000)
第2 - T + 1行:每行1个数N。(1 <= N <= 10^9)
Output
共T行,如果A获胜输出A,如果B获胜输出B。
Input示例
3
2
3
4
Output示例
B
B
A

解题思路:

这个游戏叫做Fibonacci Nim,肯定和Fibonacci数列:f[n]:1,2,3,5,8,13,21,34,55,89,… 有密切的关系。如果试验一番之后,可以猜测:先手胜当且仅当n不是Fibonacci数。换句话说,必败态构成Fibonacci数列。

这里需要借助“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。

先看看FIB数列的必败证明:

1、当i=2时,先手只能取1颗,显然必败,结论成立。

2、假设当i<=k时,结论成立。

     则当i=k+1时,f[i] = f[k]+f[k-1]。

     则我们可以把这一堆石子看成两堆,简称k堆和k-1堆。

    (一定可以看成两堆,因为假如先手第一次取的石子数大于或等于f[k-1],则后手可以直接取完f[k],因为f[k] < 2*f[k-1])

     对于k-1堆,由假设可知,不论先手怎样取,后手总能取到最后一颗。下面我们分析一下后手最后取的石子数x的情况。

     如果先手第一次取的石子数y>=f[k-1]/3,则这小堆所剩的石子数小于2y,即后手可以直接取完,此时x=f[k-1]-y,则x<=2/3*f[k-1]。

     我们来比较一下2/3*f[k-1]与1/2*f[k]的大小。即4*f[k-1]与3*f[k]的大小,由数学归纳法不难得出,后者大。

     所以我们得到,x<1/2*f[k]。

     即后手取完k-1堆后,先手不能一下取完k堆,所以游戏规则没有改变,则由假设可知,对于k堆,后手仍能取到最后一颗,所以后手必胜。

     即i=k+1时,结论依然成立。

对于不是FIB数,首先进行分解。

分解的时候,要取尽量大的Fibonacci数。

比如分解85:85在55和89之间,于是可以写成85=55+30,然后继续分解30,30在21和34之间,所以可以写成30=21+9,

依此类推,最后分解成85=55+21+8+1。

则我们可以把n写成  n = f[a1]+f[a2]+……+f[ap]。(a1>a2>……>ap)

我们令先手先取完f[ap],即最小的这一堆。由于各个f之间不连续,则a(p-1) > ap  + 1,则有f[a(p-1)] > 2*f[ap]。即后手只能取f[a(p-1)]这一堆,且不能一次取完。

此时后手相当于面临这个子游戏(只有f[a(p-1)]这一堆石子,且后手先取)的必败态,即先手一定可以取到这一堆的最后一颗石子。

同理可知,对于以后的每一堆,先手都可以取到这一堆的最后一颗石子,从而获得游戏的胜利。


代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn=50;
long long f[50];
int main()
{
    f[1]=1,f[2]=2;
    for(int i=3;i<maxn;i++)
    f[i]=f[i-1]+f[i-2];
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        int sign=1;
        for(int i=1;i<maxn;i++)
        {
            if(f[i]>n)
            break;
            if(f[i]==n)
            {
                sign=0;
                break;
            }
        }
        if(sign)
        printf("A\n");
        else
        printf("B\n");
    }
    return 0;
}




版权声明:本文为博主原创文章,未经博主允许不得转载。

51nod Bash游戏(V1,V2,V3,V4(斐波那契博弈))

标签:acm   algorithm   博弈   算法   编程   

原文地址:http://blog.csdn.net/caduca/article/details/47840643

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!