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;
}