博弈论---Nim游戏

Nim 游戏

先手必胜状态:可以走到某一个对方必败状态
先手必败状态:无论怎么走,下一个状态都是对方必胜

先给出结论:
a 1 a_1 a1^ a 2 a_2 a2 ^ a 3 a_3 a3 ^ ⋅ ⋅ ⋅ ··· ^ a n ≠ 0 时 , 先 手 必 胜 a_n\neq0时,先手必胜 an=0
a 1 a_1 a1^ a 2 a_2 a2 ^ a 3 a_3 a3 ^ ⋅ ⋅ ⋅ ··· ^ a n = 0 时 , 先 手 必 败 a_n=0时,先手必败 an=0

证明:
a 1 a_1 a1^ a 2 a_2 a2 ^ a 3 a_3 a3 ^ ⋅ ⋅ ⋅ ··· ^ a n = x ≠ 0 时 a_n=x\neq0时 an=x=0,一定存在一个数,拿掉这个数剩下的数异或起来为0
x的二进制位的最高位1在第k位,那么 a 1 − a n 中 , 必 有 一 个 数 a i 的 第 k 位 是 1 。 反 证 法 , 假 设 第 k 位 全 是 0 , 那 么 x 的 第 k 位 也 是 0 a_1-a_n中,必有一个数a_i的第k位是1。反证法,假设第k位全是0,那么x的第k位也是0 a1anaik1k0xk0
所以必定存在 a i a_i ai ^ x x x < a i <a_i <ai,所以在 a i a_i ai 中拿掉 a i − a i a_i-a_i aiai ^ x x x,那么 a i a_i ai变成了 a i a_i ai ^ x x x

所以 a 1 a_1 a1^ a 2 a_2 a2 ^ a 3 a_3 a3 ^ ⋅ ⋅ ⋅ ··· ^ a i a_i ai ^ ⋅ ⋅ ⋅ ··· ^ a n = x a_n=x an=x^ x = 0 x=0 x=0变成了
a 1 a_1 a1^ a 2 a_2 a2 ^ a 3 a_3 a3 ^ ⋅ ⋅ ⋅ ··· ^ a i a_i ai ^ x ⋅ ⋅ ⋅ ··· ^ a n = x a_n=x an=x^ x = 0 x=0 x=0
拿完之和剩余所有的数异或起来变成了 0 0 0

a 1 a_1 a1^ a 2 a_2 a2 ^ a 3 a_3 a3 ^ ⋅ ⋅ ⋅ ··· ^ a i a_i ai ^ ⋅ ⋅ ⋅ ··· ^ a n = 0 a_n=0 an=0,不管怎么拿,剩下的数异或起来一定不为0$
假设把 a i a_i ai 拿了之后, a i a_i ai 变成了 a i t a_i^t ait
反证法:假设 a 1 a_1 a1^ a 2 a_2 a2 ^ a 3 a_3 a3 ^ ⋅ ⋅ ⋅ ··· ^ a i t a_i^t ait ^ ⋅ ⋅ ⋅ ··· ^ a n = 0 a_n=0 an=0,把这个式子与没拿之前的异或起来,
就有 a i a_i ai ^ a i t = 0 a_i^t=0 ait=0,说明 a i = a i t a_i=a_i^t ai=ait,那么矛盾,所以拿完之和异或起来一定不为 0 0 0

因为石子是有限的,所以在经过有限次拿之后,最后所有石子一定会拿完,所以要保证赢必须保证拿完之后的所有数的异或值是 0 0 0,让别人无石子可拿。

最最经典的Nim游戏

模板题链接:AcWing 891. Nim游戏
代码如下:

#include <iostream>

using namespace std;

int n,x;

int main()
{
    int res=0;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d",&x);
        res^=x;
    }
    
    if(res)
        printf("Yes");
    else
        printf("No");
        
    return 0;
}

经典Nim游戏的变形:AcWing 892. 台阶-Nim游戏
这道题也是一样的,其实只用看奇数阶的台阶的异或值就行了

SG函数与mex

mex运算

m e x ( m i n i m a l e x c l u d a n t ) mex(minimal excludant) mex(minimalexcludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如 m e x mex mex{ 0 , 1 , 2 , 4 0,1,2,4 0,1,2,4} = 3 =3 =3 m e x mex mex{ 1 , 3 , 5 1,3,5 1,3,5} = 0 =0 =0 m e x mex mex{} = 0 =0 =0

SG函数

S G ( x ) = m e x SG(x)=mex SG(x)=mex{ y 1 , y 2 , y 3 , ⋅ ⋅ ⋅ , y n y_1,y_2,y_3,···,y_n y1,y2,y3,,yn},其中   y 1 , y 2 , y 3 , ⋅ ⋅ ⋅ , y n   \,y_1,y_2,y_3,···,y_n\, y1,y2,y3,,yn   x   \,x\, x能到达的子局面
S G ( 终 点 ) = 0 SG(终点)=0 SG()=0
在SG图中,任何一个非零状态一定能到   0 \,0 0,任何一个   0 \,0 0状态一定不能到达非零状态

比如,对于一个有 10 10 10 个石子的石头堆,每次只能拿 2 2 2 个或 5 5 5 个,那么这个 S G SG SG 图如下,其中红色的为 S G SG SG 的值
在这里插入图片描述
模板题:AcWing 893. 集合-Nim游戏
注意点:①:要用集合来存
②: f [ x ] ! = − 1 f[x]!=-1 f[x]!=1 的时候说明 x x x 这个点已经存在 s g sg sg 值了,记忆化,可以直接返回
③:最后一步返回 f [ x ] = i f[x]=i f[x]=i 的时候,包含处理了 0 0 0这种情况了

#include<bits/stdc++.h>

using namespace std;

const int N = 110, M = 1e4 + 10;
int f[M], se[N];//f用来存储每个数的sg值,se用来存储集合里面的数
int k, s, n, h;

int sg(int x)
{
    if(f[x] != -1)
        return f[x];
    set<int> S;
    for(int i = 1; i <= k; ++i)
    {
        if(x >= se[i])
            S.insert(sg(x - se[i]));
    }

    for(int i = 0; i < M; ++i)
        if(!S.count(i))
            return f[x] = i;
}

int main()
{
    memset(f, -1, sizeof(f));
    scanf("%d", &k);
    for(int i = 1; i <= k; ++i)
        scanf("%d", &se[i]);

    scanf("%d", &n);
    int res = 0;
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d", &h);
        res ^= sg(h);
    }

    if(res)
        printf("Yes");
    else
        printf("No");

    return 0;
}

SG函数变形的一道题:AcWing 894. 拆分-Nim游戏
这道题就是把一个数拆成了两个数,本质上还是SG函数
在这里插入图片描述
核心就是:当前异或值不为0的,一定可以经过操作变成0
当前异或值为0的,经过操作后的异或值一定不是0
经过有限次操作之后,所有的数一定都可以变成0,即游戏结束

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值