模板 - 数学 - 博弈论
1.要从必胜或必败的局面反推
2.SG函数
只要当前状态可以转移到的状态中有一个是败态,那么当前状态就是胜态。胜态为N。
如果当前状态可以转移到的所有状态都是胜态,那么当前状态就是败态。败态为P。
sg函数为每个状态赋一个自然数的值,这个值为除这个状态的后继外的最小自然数。首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
从图的汇点开始反推,可知汇点(第一个败态)的sg值为0。
性质:
败态等价于sg值为0。
游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。
类似这样用
```cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=1000005;
const int N=1000005;
//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
vector<int> nextofSG[MAXN];
int f[N],SG[MAXN],S[MAXN];
void  getSG(int n){
    for(int i = 1; i <= n; i++){
        int l=nextofSG[i].size();
        //后继状态 最多有l 种
        for(int j=0;j<=l;j++){
            S[j]=0;
        }
        for(auto vi:nextofSG[i]){
            //vi:从i状态能取走的石子数
            S[SG[i-vi]]=1;
        }
        for(int j=0;j<=l;j++){
            if(!S[j]){
                SG[i] = j;
                break;
            }
        }
        cout<<"SG["<<i<<"]="<<SG[i]<<endl;
    }
}
int NNN=100;
void enque(int id){
    int B=4;
    int cur=1;
    while(id+cur<=NNN){
        nextofSG[id+cur].push_back(id);
        cur*=B;
        if(B==1)
            break;
    }
}
int main() {
#ifdef Yinku
    freopen("Yinku.in", "r", stdin);
#endif // Yinku
    /*while(~scanf("%d%d",&b,&n)){
        if(n==1||ispow(n,b))
            fi();
        else{
        }
    }*/
    for(int i=0;i<=NNN;++i){
        enque(i);
    }
    getSG(NNN);
}
```

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号