Grundy数
定义:当前状态的Grundy是除任意一步所能转移到的Grundy以外的最小非负整数
理解:本状态可以转移到的状态肯定与本状态相反,而所以找第一个不能转移到的状态就是其Grundy值
题目:hdu1847
两人玩;总共n张牌;双方轮流抓牌;每人每次抓牌的个数只能是2的幂次(1,2,4,8,16…)
抓完者胜。问谁胜
#include <bits/stdc++.h>
using namespace std;
int n,arr[15],sg[1005];//可拿牌数,枚举的SG个数
int mex(int x){ //判X个石此状态先手能否赢
if(sg[x]!=-1)return sg[x];//如果已更新,直接返回
bool vis[1005]; //定义访问标记
for(int i=0;i<1005;i++) //标记遍历
vis[i]=false; //初始化为假
for(int i=0;i<=10;i++){ //作为ARR的下标即2的幂次
int tmp=x-arr[i]; //当前X可到达的状态
if(tmp<0)break; //如果石子数不够拿就跳出(后面高次也不够拿)
sg[tmp]=mex(tmp); //递归求解更新SG值
vis[sg[tmp]]=true; //标记这个值已访问,表示当前X个石,采用某种拿法后可以转移到此状态
}
for(int i=0;;i++) //找第一个未VIS石子数状态,即当X块石不可能转移到的状态
if(!vis[i]){sg[x]=i;break;} //由低位起扫,第一个必为最小,即当前状态SG
return sg[x]; //返回
}
int main(){
arr[0]=1; //2的0次就是1
for(int i=1;i<=10;i++) //枚举阶次
arr[i]=arr[i-1]*2; //每次乘2
while(cin>>n) //找N的SG值
memset(sg,-1,sizeof(sg)); //全部初始化为-1
if(mex(n))puts("1st win"); //看N能否返回1
else puts("2nd win"); //同里
}//现在只有一堆石可直接判,如果有多堆石,见下例
return 0;
}
关于算法:
第一种是递归
第二种是集合
集合是由0起逐个SG往上求
递归没有for一步,但求SG[N]时也是会把前面0至N-1的SG全部求出
原理相通,求第一个不可转移状态也是
模板题:
K个数字可取,用a[]存
N堆石子个数,用n[]存
问谁胜
#include<bits/stdc++.h>
using namespace std;
int grundy[10000];
int a[100],x[1000000], n, k;
int main(){
cin>>n>>k; //硬币堆数,可取硬币数组大小
int i,j,max_x; //循环变量
for(i=0;i<n;i++)cin >> x[i]; //输入每堆硬币数
for(i=0; i<k; i++)cin >> a[i]; //输入每堆可取数
for(i=0;i<n;i++)max_x=max(max_x,x[i]); //求出最大的堆数
for(i=0; i<=max_x; i++){ //逐个SG求
set<int> s; //开集合
for(j=0; j<k; j++) //扫一次每个数
if(a[j] <= i) //a[j]比i小,即当前i块石取走a[j]块可行
s.insert(grundy[i-a[j]]);//就取走并压入取走后的grundy值(前面已求)
int g = 0; //最小非负整数,找S不包含的最小非负整数
while(s.count(g))g++; //找集集里面的GRUNDY值当COUNT到的值为0,表示没有,就跳出
grundy[i] = g; //得到当前I状态的GRUNDY值
}
int result = 0; //异或判断结果
for(i=0; i<n; i++)result ^= grundy[x[i]];//因为有多堆,所以学NIM一样异或即可
if(result)cout<<"fist win"<<endl; //结果为值就先手赢
else cout <<"second win"<<endl; //结果为假就后手赢
return 0;
}
2019年2月22日复习笔记:
假设现在只有一堆石,共6块,每次可取1,2块
首先gr0=0,0块石是必败状态,因为不能拿
然后求gr1,1块石时的状态他可以转移到gr0,也就是0,因为集合中有0,所以gr1的值是1,就是说第一个不可以转移到的状态就是1,第一个不可以转移到的状态意味着与当前态是胜败一致的
再求gr2,可以转移到gr0与gr1,集合中有0,1所以gr2的值是2
再求gr3,可以转移到gr1与gr2,集合中有1,2所以gr3的值是0,就是说剩下3块石时的胜败与只有0块石时的胜败是一致的
再求gr4,可以转移到gr2与gr3,集合中有2,0所以gr4的值是1,就是说剩下4块石时的胜败与只有1块石时的胜败是一致的
再求gr5,可以转移到gr3与gr4,集合中有0,1所以gr5的值是2,就是说剩下5块石时的胜败与只有2块石时的胜败是一致的
再求gr6,可以转移到gr4与gr5,集合中有1,2所以gr6的值是0,就是说剩下6块石时的胜败与只有0块石时的胜败是一致的
如果只有一堆石,则只要gr值是0就表示输,非0就是胜
如果是有多堆石,则异或gr值是0就表示输,非0就是胜