Grundy数

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就是胜

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值