数学公式:博弈论

res得出的答案是在先手的情况下,先拿几个石头,在后面的回合只模仿对手拿的数量。

Nim游戏:

cpp 复制代码
#include <iostream>
#include <cstdio>
using namespace std;

/*
先手必胜状态:先手操作完,可以走到某一个必败状态
先手必败状态:先手操作完,走不到任何一个必败状态
先手必败状态:a1 ^ a2 ^ a3 ^ ... ^an = 0
先手必胜状态:a1 ^ a2 ^ a3 ^ ... ^an ≠ 0
*/

int main()
{
    int n;
    scanf("%d", &n);
    int res = 0;
    for(int i = 0; i < n; i++)
    {
        int x;
        scanf("%d", &x);
        res ^= x;//每堆石头都异或一下答案,有几堆石头就更新几次答案
    }
    if(res == 0) printf("No\n");
    else printf("Yes\n");
}

台阶-Nim游戏:

注意:因为最后要把石子都放到地面,地面是第0层(偶数层)

cpp 复制代码
#include <iostream>

using namespace std;

int main()
{
    int res = 0;
    int n;
    scanf("%d",&n);

    for(int i = 1 ; i <= n ; i++)
    {
        int x;
        scanf("%d",&x);
        if(i % 2) res ^= x;//只要奇数的台阶的值异或值为0就必胜
    }

    if(res) printf("Yes\n");
    else printf("No\n");
    return 0;
}

集合-Nim游戏:

1.Mex运算:

设S表示一个非负整数集合.定义mex(S)为求出不属于集合S的最小非负整数运算,即:
mes(S)=min{x};
例如:S={0,1,2,4},那么mes(S)=3;

2.SG函数

在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1,y2,····yk,定义SG(x)的后记节点y1,y2,····
yk的SG函数值构成的集合在执行mex运算的结果,即:
SG(x)=mex({SG(y1),SG(y2)····SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即 SG(G)=SG(s).

3.有向图游戏的和

设G1,G2,····,Gm是m个有向图游戏.定义有向图游戏G,他的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步.G被称为有向图游戏G1,G2,·····,Gm的和.
有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数的异或和,即:
SG(G)=SG(G1)xorSG(G2)xor···xor SG(Gm)

如:某个石堆:每次只拿2或者5个,一开始有10个,把所有可能画出来,计算SG(10)的结果,是1则必胜,是0则必败

题目要求是只能有两种操作:2和5;一共有三个石堆:2 4 7

cpp 复制代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<set>

using namespace std;

const int N=110,M=10010;
int n,m;
int f[M],s[N];//s存储的是可供选择的集合的个数,f存储的是所有可能出现过的情况的sg值

int sg(int x)
{
    //因为取石子数目的集合是已经确定了的,所以每个数的sg值也都是确定的,如果存储过了,直接返回即可
    if(f[x]!=-1) return f[x];

    set<int> S;
    //set代表的是有序集合(注:因为在函数内部定义,所以下一次递归中的S不与本次相同)
    for(int i=0;i<m;i++)
    {
        int sum=s[i];
        if(x>=sum) S.insert(sg(x-sum));
        //先延伸到终点的sg值后,再从后往前排查出所有数的sg值
    }

    for(int i=0;;i++)
    //循环完之后可以进行选出最小的没有出现的自然数的操作
     if(!S.count(i))
      return f[x]=i;
}

int main()
{
    scanf("%d",&m);
    for(int i=0;i<m;i++)
    scanf("%d",&s[i]);

    scanf("%d",&n);
    memset(f,-1,sizeof(f));//初始化f均为-1,方便在sg函数中查看x是否被记录过

    int res=0;
    for(int i=0;i<n;i++)
    {
        int x;
        scanf("%d",&x);
        res^=sg(x);//观察异或值的变化,基本原理与Nim游戏相同
    }

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

    return 0;
}

拆分-Nim游戏:

注意:该题的意思是:取走一个堆,再造两个更小的堆。比如取走50的堆,新造49和48的堆。

cpp 复制代码
#include <iostream>
#include <cstring>
#include <unordered_set>

using namespace std;

const int N = 110;

int n;
int f[N];
unordered_set<int> S;

int sg(int x)
{
    if(f[x] != -1) return f[x];//因为取石子数目的集合是已经确定了的,所以每个数的sg值也都是确定的,如果存储过了,直接返回即可
    
    for(int i = 0 ; i < x ; i++)
        for(int j = 0 ; j <= i ; j++)//规定j不大于i,避免重复
            S.insert(sg(i) ^ sg(j));//先延伸到终点的sg值后,再从后往前排查出所有数的sg值
            //相当于一个局面拆分成了两个局面,由SG函数理论,多个独立局面的SG值,等于这些局面SG值的异或和

    for(int i = 0 ; ; i++)
        if(!S.count(i))//循环完之后可以进行选出最小的没有出现的自然数的操作
            return f[x] = i;
}

int main()
{
    memset(f , -1 , sizeof f);//初始化f均为-1,方便在sg函数中查看x是否被记录过

    scanf("%d",&n);
    int res = 0;
    while(n--)
    {
        int x;
        scanf("%d",&x);
        res ^= sg(x);//观察异或值的变化,基本原理与Nim游戏相同
    }

    if(res) printf("Yes\n");
    else printf("No\n");
    return 0;
}
相关推荐
动感光博9 分钟前
Unity(URP渲染管线)的后处理、动画制作、虚拟相机(Virtual Camera)
开发语言·人工智能·计算机视觉·unity·c#·游戏引擎
丶Darling.25 分钟前
Day119 | 灵神 | 二叉树 | 二叉树的最近共公共祖先
数据结构·c++·算法·二叉树
蚰蜒螟1 小时前
深入解析JVM字节码解释器执行流程(OpenJDK 17源码实现)
开发语言·jvm·python
keke101 小时前
Java【14_2】接口(Comparable和Comparator)、内部类
java·开发语言
思茂信息1 小时前
CST软件对OPERA&CST软件联合仿真汽车无线充电站对人体的影响
c语言·开发语言·人工智能·matlab·汽车·软件构建
CN.LG1 小时前
Java 乘号来重复字符串的功能
java·开发语言
L_cl1 小时前
【Python 算法零基础 3.递推】
算法
川川菜鸟1 小时前
2025长三角数学建模C题完整思路
c语言·开发语言·数学建模
萌新下岸多多关照1 小时前
Java中synchronized 关键字
java·开发语言
醍醐三叶1 小时前
C++文件操作--2 二进制文件操作
开发语言·c++