公平组合游戏(ICG)需要满足以下条件
对于一个ICG,我们定义一个函数 , 即一个状态下的sg函数值为这个状态所能转移到的所有状态的sg,再取mex
如果 则状态x为必败态,反之为必胜态
对于多个ICG,我们有Sprague-Grundy定理:
设 为ICG的状态,则:
即取石子游戏,有以下情况:
k-nim是Nim的变形,即每次可以从不超过k个堆中取出任意个石子,不能取的一方为输。
首先我们把每个堆石子的数量表示为二进制,然后对所有的数逐位判断。如果有一位,所有的数这一位上为1的数量不为0,则先手必胜。反之先手必输
和一般的Nim差不多,不过变成了把最后的石子取完的一方为输。我们有SJ定理:
先手必胜,当且仅当:
(1)单一游戏中有一个SG大于1,且整个游戏SG不为0
(2)单一游戏中所有的SG小于等于1,且整个游戏SG为0
P2148 [SDOI2009] E&D
首先打表找出sg函数的规律
cpp1234567891011121314151617181920212223242526272829int sg[21][21],vis[21][21];
const int n=20;
int dfs(int x,int y){
if(vis[x][y]||vis[y][x]) return sg[x][y];
if(x==1&&y==1) return 0;
vector<int> v;
//case1
for(int i=1;i<x;i++){
v.push_back(dfs(i,x-i));
}
//case2
for(int i=1;i<y;i++){
v.push_back(dfs(i,y-i));
}
sort(v.begin(),v.end());
int p=0;
for(int i:v){
if(i<p) continue;
if(i!=p){
break;
}
p++;
}
vis[x][y]=vis[y][x]=true;
sg[x][y]=sg[y][x]=p;
return p;
}
得到下表
找到规律
实现sg函数
cpp12345678int getSG(int x,int y){
if(x%2==1&&y%2==1){
return 0;
}
if(y%2==0) swap(x,y);
if(y%2==1) y++;
return getSG(x/2,y/2)+1;
}
还可以检查一下
cpp123456789for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(getSG(i,j)==dfs(i,j)){
cout<<"accept"<<endl;
}else{
cout<<"wa"<<endl;
}
}
}