sg定理 如果所有的sg值异或起来非0那该方必胜。
可以通过证明,证明如下:
假设所有sg的异或值为x sg(i)能变小成为sg(i)x
然后带入就变成0了 最后游戏结束的条件也是0 那么在有胜势的一方可以通过精妙的操作让下方永远为0 所以下方必输,太厉害了,佩服佩服。
下面是几道acwing的例题。
题目1
这道题可以抛砖引玉,引出博弈论,思路就是异或所有值,如果不是0必胜,反之必输。
题目2
题意大致就是n级台阶 每个台阶n个石子,每次操作只能把一些石子往下移一个格,问先出手的那方能赢吗?
思路:这道题只需要考虑奇数级台阶就行了,无论下家移动奇数或者偶数的台阶,我们都能控制奇数台阶异或值为0,下家必输。
题目3
这道题限定了拿取的个数。从这题开始真正引入了sg值的概念。
思路 设一个f数组储存sg值,分别在每个集合内枚举能减去的个数,建立个哈希表储存剩余的个数,用mex定理找出该点走不到的最小自然数,把所有集合的sg异或起来如果大于等于1就赢否则输了。另外,同一道题目相同数量的点他们的sg值是相等的。

#include<iostream>
#include<cstring>
#include<unordered_set>
using namespace std;
const   int N=110,M=10010;
int f[M];
int p[N];  
int k;
int m;
int sg(int x)
{
   
    if(f[x]!=-1)
    return f[x];
    unordered_set<int>S;
    for(int i=0;i<k;i++)
       if(x-p[i]>=0)    S.insert(sg(x-p[i]));
    for(int i=0;;i++)
    if(!S.count(i))
    return f[x]=i;
}
int main()
{
   

    cin>>k;
    memset(f,-1,sizeof(f));
    for(int i=0;i<k;i++)
    cin>>p[i];
    int res=0;
    cin>>m;
    while(m--)
    {
   
        int x;
        cin>>x;
        res^=sg(x);
    }
    if(res==0)  printf("No\n");
    else    printf("Yes\n");
    return 0;
}

题目4
这道题题意是能拿走某堆并且拆分成规模更小的两堆,两堆的数量可以大于该堆,这题的思路emmm,枚举到该集合能分成的堆,把两堆的sg值异或就是整个小局面的sg值啦,然后同理用哈希表存下来sg值。再把所有集合的sg值异或起来,就能同理判断是否先手能赢了。

#include<iostream>
#include<cstring>
#include<unordered_set>
using namespace std;
const   int N=100010;
int f[N];
int sg(int x)
{
   
    if(f[x]!=-1)    
    return f[x];
    unordered_set<int>S;
    for(int i=0;i<x;i++)
    for(int j=0;j<x;j++)
    S.insert(sg(i)^sg(j));
    for(int i=0;;i++)
    if(!S.count(i))
    return f[x]=i;
}
int main()
{
   
    int n;
    cin>>n;
    memset(f,-1,sizeof(f));
    int res=0;
    while(n--)
    {
   
        int x;
        cin>>x;
        res^=sg(x);
    }
    if(res) printf("Yes\n");
    else    printf("No\n");
    return 0;
}