SG函数 , 需要尼姆博奕(Nimm Game) 作为基础
\\\;
SG函数其实非常的简单 , 我初学的时候也被网上的花哨的bolg给吓到了
单个游戏的SG
知识点1 :
学完之后的SG → \to →判断一个状态的SG值为非0值 , 便为一个必胜态 ; 只要SG=0才是必败态
知识点2 :
对集合函数mex → \to → 返回未在集合中的最小非负整数 , 例如mex{1,2}=0 , mex{0,2}=1 , mex{}=0
知识点3 :
S的后继状态代表对状态S进行一次操作后产生的状态
状态x的SG函数值 → \to → SG(x)=mex{S|S为x后继状态}
归纳 :
预处理一个状态x的SG值 , 只要处理出其后继状态的SG值 , 然后找到这些SG值中没有出现过的非负整数 , 就是x的SG值
代码实现 :
→
\to
→例题
题意 : 多个堆 , 有一个m个元素的集合 , 每次操作可以从堆中remove的数量只能是集合中的数
首先是暴力打表 , 从小的状态处理到大的状态 , 枚举所有操作得出的下一状态(从小到大所以保证了下一状态已经处理出了) , 然后求这些状态的mex便是当前状态的SG
首选暴力打表,因为时间是一样的
int sg[10009];
bool vis[10009];
void init(int*s,int m){
//本题有m个操作,分别为拿走s[i]个珠子
memset(sg,0,sizeof(sg));
for(int i=1;i<=10000;i++){//10000为珠子数量上限
memset(vis,0,sizeof(vis));
//因为memset vis数组的次数太多了, 所以只能用bool, 用int会TLE
for(int j=1;j<=m&&s[j]<=i;j++){//遍历所有操作
vis[sg[i-s[j]]]=1;//维护mex,不是vis状态而是状态的SG值
}
for(int j=0;j<=10000;j++){//找最小
if(!vis[j]){sg[i]=j;break;}
}
}
}
这里给大家分享几种dfs的写法 , 如果不知道sg的范围只能开状态数的范围了,会MLE
但是我后来测试了一下发现SG值不会超过40 , 所以vis只需要开40就可以过了
//如果是dfs的话,下面的vis数组不能和上面的用一个,不然会错
//所以有两种做法
//做法一:二维vis
bool vis[状态数][sg数];//vis[10001][40]
int dfs(int i,int *s,int m){
if(sg[i]!=-1)return sg[i];
for(int j=1;j<=m&&s[j]<=i;j++){
vis[i][dfs(i-s[j],s,m)]=1;
}
for(int j=0;;j++){
if(!vis[i][j])return sg[i]=j;
}
}
int main(){
memset(sg,-1,sizeof(sg));
for(int i=1;i<=n;i++)scanf("%d",&a),Nim=dfs(a,s,m);
//不用预处理直接dfs,遇到没有处理过的状态才处理
}
//做法二:dfs时开vis数组
int dfs(int i,int *s,int m){
if(sg[i]!=-1)return sg[i];
int *vis=new int[40];
for(int j=0;j<40;j++)vis[j]=0;//函数内不能用sizeof(指针)
for(int j=1;j<=m&&s[j]<=i;j++){
vis[dfs(i-s[j],s,m)]=1;
}
for(int j=0;;j++){
if(!vis[j]){sg[i]=j;break;}
}
delete vis;
return sg[i];
}
int main(){
memset(sg,-1,sizeof(sg));
for(int i=1;i<=n;i++)scanf("%d",&a),Nim=dfs(a,s,m);
//不用预处理直接dfs,遇到没有处理过的状态才处理
}
多个游戏的SG
什么是多个游戏? 比如有一堆石子给双方取就是一个游戏, 多个堆给双方取就是多个游戏, 你有没有觉得和Nim博弈的描述很像?
对于多个游戏 , 处理出每个游戏现在状态的SG值 , 然后和Nim博弈那样每个SG值异或起来就是整个的SG值 . 当然和Nim博弈一样 , 异或后的结果如果是0那么就是必败态 , 否则为必胜态
假设有个堆不能取完
只需要求出 N − 1 N-1 N−1的SG即可,也就是说只剩下1个时必败。
例题代码
//打表版
#include<bits/stdc++.h>
using namespace std;
int read(){ int ans=0; char last=' ',ch=getchar();
while(ch<'0' || ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}
int sg[10009];
bool vis[40];
void init(int*s,int m){
memset(sg,0,sizeof(sg));
for(int i=1;i<=10000;i++){
memset(vis,0,sizeof(vis));
for(int j=1;j<=m&&s[j]<=i;j++){
vis[sg[i-s[j]]]=1;
}
for(int j=0;;j++){
if(!vis[j]){sg[i]=j;break;}
}
}
}
int main(){
int m;
int s[109];
while(scanf("%d",&m)==1){if(!m)break;
for(int i=1;i<=m;i++)scanf("%d",&s[i]);
sort(s+1,s+1+m);
init(s,m);
int t=read();
while(t--){
int Nim=0,a;
int n=read();
for(int i=1;i<=n;i++)scanf("%d",&a),Nim^=sg[a];
if(Nim==0)printf("L");
else printf("W");
}
printf("\n");
}
}
//dfs版
#include<bits/stdc++.h>
using namespace std;
int read(){ int ans=0; char last=' ',ch=getchar();
while(ch<'0' || ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}
int sg[10009];
int dfs(int i,int *s,int m){
if(sg[i]!=-1)return sg[i];
int *vis=new int[40];
for(int j=0;j<40;j++)vis[j]=0;
for(int j=1;j<=m&&s[j]<=i;j++){
vis[dfs(i-s[j],s,m)]=1;
}
for(int j=0;;j++){
if(!vis[j]){sg[i]=j;break;}
}
delete vis;
return sg[i];
}
int main(){
int m;
int s[109];
while(scanf("%d",&m)==1){if(!m)break;
for(int i=1;i<=m;i++)scanf("%d",&s[i]);
sort(s+1,s+1+m);
memset(sg,-1,sizeof(sg));
int t=read();
while(t--){
int Nim=0,a;
int n=read();
for(int i=1;i<=n;i++)scanf("%d",&a),Nim^=dfs(a,s,m);
if(Nim==0)printf("L");
else printf("W");
}
printf("\n");
}
}
最后有两个常见的SG值:
- 当i个石子任意取时,SG=i
- 当i个石子要取1~m时,SG=i%(m+1)