模板 - 数学 - 博弈论

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

```

 

posted @ 2019-01-27 23:15  韵意  阅读(215)  评论(0编辑  收藏  举报