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

猜你喜欢

转载自blog.csdn.net/cj1064789374/article/details/84922050