数学公式:博弈论

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;
}
相关推荐
未知陨落2 分钟前
数据结构——二叉搜索树
开发语言·数据结构·c++·二叉搜索树
丶Darling.3 分钟前
Day44 | 动态规划 :状态机DP 买卖股票的最佳时机IV&&买卖股票的最佳时机III
算法·动态规划
大波V53 分钟前
设计模式-参考的雷丰阳老师直播课
java·开发语言·设计模式
无敌最俊朗@17 分钟前
unity3d————接口基础知识点
开发语言·c#
一丝晨光43 分钟前
gcc 1.c和g++ 1.c编译阶段有什么区别?如何知道g++编译默认会定义_GNU_SOURCE?
c语言·开发语言·c++·gnu·clang·gcc·g++
南城花随雪。1 小时前
Spring框架之装饰者模式 (Decorator Pattern)
java·开发语言·装饰器模式
究极无敌暴龙战神X1 小时前
前端学习之ES6+
开发语言·javascript·ecmascript
虞书欣的61 小时前
Python小游戏24——小恐龙躲避游戏
开发语言·python·游戏·小程序·pygame
TN_stark9321 小时前
多进程/线程并发服务器
服务器·算法·php
FHYAAAX1 小时前
【机器学习】任务十:从函数分析到机器学习应用与BP神经网络
开发语言·python