数学公式:博弈论

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

Nim游戏:

#include 
#include 
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层(偶数层)

#include 

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

#include
#include
#include
#include

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 S;
    //set代表的是有序集合(注:因为在函数内部定义,所以下一次递归中的S不与本次相同)
    for(int i=0;i=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

拆分-Nim游戏:

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

#include 
#include 
#include 

using namespace std;

const int N = 110;

int n;
int f[N];
unordered_set 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;
}

你可能感兴趣的:(算法基础,c++,算法,开发语言)