数学公式:博弈论

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;
}
相关推荐
pipixia20033几秒前
C++ 面试高频:namespace 的 6 个知识点,从 using 到匿名命名空间
c语言·学习·算法·嵌入式·命名空间·c++面试·匿名命名空间
lunar_coder16 分钟前
【C++】如何解决智能指针的析构函数,如何实现shared_ptr的引用计数以及解决shared_ptr的循环引用
c++·1024程序员节
白白白小纯1 小时前
C++进阶.多态
c++
2401_885885041 小时前
国际语音C#接口怎么写?.NET环境下实现国际语音验证码批量下发
开发语言·c#·.net
Navigator_Z1 小时前
LeetCode //C - 1300. Sum of Mutated Array Closest to Target
c语言·算法·leetcode
SZYSK1 小时前
鸿蒙人脸识别算法放在终端里:和外挂授权 SDK 差在排障路径
物联网·算法·华为·harmonyos
程序猿乐锅1 小时前
【从0-1学习Python | 第七篇】Python 面向对象基础
开发语言·笔记·后端·python·学习·1024程序员节
Wang's Blog2 小时前
Java 项目实战: 交友项目 - 查询圈子个人动态:查询我的动态需求分析
java·开发语言·交友
凯瑟琳.奥古斯特2 小时前
力扣1016高效解法:剪枝优化技巧
开发语言·c++·算法·leetcode·职场和发展
xiangyun612 小时前
C++ priority_queue 底层:手撕二叉堆与 Top K 问题
c++·算法·面试·stl·1024程序员节