51node-Bash game V1V2V3V4

51node-Bash gameV1V2V3V4

Bash game v1:

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次最少拿1颗,最多拿K颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N和K,问最后谁能赢得比赛。
例如N = 3,K = 2。无论A如何拿,B都可以拿到最后1颗石子。

解:
最基本的Bash game,显然,只有n % (k+1) == 0时B才能赢。否则A将剩余的石子取到%(k+1)==0的数字,每次只要取[k+1-(B取得数字)]即可获胜。

#include <iostream>
#include <cstdio>
using namespace std;

int main()
{
    int n, k, T;
    cin>>T;
    while (T--) {
      cin>>n>>k;
      if (n % (k+1) == 0) cout<<"B"<<endl;
      else cout<<"A"<<endl;
    }
    return 0;
}

Bash game V2

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次只能拿1,3,4颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。
例如N = 2。A只能拿1颗,所以B可以拿到最后1颗石子。

解:
我们先要知道:
(1) SG函数:对于任意状态的x,它的SG函数值g(x)=mex{g(y)|y是x的后续状态}。其中mex是一个对于非负整数集的运算,mex(S)为S中没有出现的最小正整数。对于一个终止状态,它没有后续状态,所以它的SG函数值为0.
(2)终止状态:即当前状态下游戏不能继续进行。
(3)对于一个状态,如果这个状态的SG值==0,那么这个状态是P态,即当前一手必败,否则就是N态,即当前一手必胜

于是,我们用Si表示还剩i个物品的状态。
g(s0)=0
g(s1)=mex{g(s0)}=1
g(s2)=mex{g(s1)}=0
g(s3)=mex{g(s2),g(s0)}=1
g(s4)=2
g(s5)=3
g(s6)=2
~~~~~~~~~~~~~~~~~~~
g(s7)=0
g(s8)=1
g(s9)=0
g(s10)=1
……
发现循环节

#include <iostream>
using namespace std;

int main()
{
    int T, n, p[10] = {0, 1, 0, 1, 2, 3, 2};
    cin>>T;
    while (T--) {
      cin>>n;
      if (p[n % 7]) cout<<"A"<<endl;
      else cout<<"B"<<endl;
    } 
    return 0;
}

Bash game V3

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次拿的数量只能是2的正整数次幂,比如(1,2,4,8,16….),拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。
例如N = 3。A只能拿1颗或2颗,所以B可以拿到最后1颗石子。(输入的N可能为大数)

解:同Bash gameV2

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAXN = 1020;

char num[MAXN];

int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
      int sum = 0;
      scanf("%s", num);
      int len = strlen(num);
      for (int i = 0; i < len; i++) 
        sum += num[i] - '0';
      if (sum % 3) printf("A\n");
      else printf("B\n");
    }
    return 0;
}

Bash game V4

有一堆石子共有N个。A B两个人轮流拿,A先拿。每次拿的数量最少1个,最多不超过对手上一次拿的数量的2倍(A第1次拿时要求不能全拿走)。拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。
例如N = 3。A只能拿1颗或2颗,所以B可以拿到最后1颗石子。

解:这里就不能用SG函数找规律了,于是,我选择暴力打表找规律。将最后A胜定义游戏值为1,B胜为-1,轮到A走即为Max状态,否则为Min状态。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100000;

int n, tep, ans;

struct Status{
    int less, now, fa, got, id, val;
    void init(int a, int b, int c, int d, int e, int f){
        less = a, now = b, fa = c, got = d, id = e, val = f;
    }
    void print(){
        printf("编号:%d ## fa是:%d, 这一步是%d走,还剩%d棋子,父节点取走%d个,这个节点的val是%d\n", id, fa, now, less, got, val);
    }
}Sta[MAXN];

int dfs(Status& x){
    //这一步就是终止状态
    if (x.got * 2 >= x.less && x.fa != -1) {
      if (x.now == 1) x.val = 1;
      else x.val = -1;
      return x.val; 
    } 
    //初始化x.val的值 
    if (x.now) x.val = -1;
    else x.val = 1;
    for (int i = 1; i <= min(x.got * 2, n-1); i++) {
      int flag = ++tep;
      Sta[flag].init(x.less-i, x.now^1, x.id, i, flag, 0);
      if (x.now)    //MAX节点 
        x.val = max(dfs(Sta[flag]), x.val);
      else          //MIN节点 
        x.val = min(dfs(Sta[flag]), x.val);
    }
    return x.val;
}

int main()
{
    for (int i = 2; i <= 21; i++) {
      n = i, tep = 1;
      Sta[1].init(n, 1, -1, 10000, 1, 0);
      if (dfs(Sta[1]) == 1) printf("%d : A\n", i);
      else if (Sta[1].val == -1) printf("%d : B\n", i);
    }
    return 0;
}

通过对博弈树的搜索(这里其实可以用alpha-beta剪枝),我们发现这是一个fibonacci博弈。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
using namespace std;

set<int> fib;

int main()
{
    //init---------
    int a = 1, b = 1, c;
    fib.insert(1);
    while (c <= 1e9) {
      c = a;
      a = b;
      b = c + b;
      fib.insert(b);
    }
    //--------------
    int n, T;
    cin>>T;
    while (T--) {
      cin>>n;
      if (fib.count(n)) cout<<"B"<<endl;
      else cout<<"A"<<endl;
    }
    return 0;
} 

对其证明(引用自http://www.cnblogs.com/Ritchie/p/5762320.html

用第二数学归纳法证明:
为了方便,我们将n记为f[i]。
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时,结论依然成立。

那么,当n不是Fibonacci数的时候,情况又是怎样的呢?
这里需要借助“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。
关于这个定理的证明,感兴趣的同学可以在网上搜索相关资料,这里不再详述。
分解的时候,要取尽量大的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)]这一堆石子,且后手先取)的必败态,即先手一定可以取到这一堆的最后一颗石子。
同理可知,对于以后的每一堆,先手都可以取到这一堆的
最后一颗石子,从而获得游戏的胜利。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值