先手必胜态 ( N : n o w w i n ) (N:now win) (N:nowwin)和先手必败态 ( P : p r e v i o u s w i n ) (P:previous win) (P:previouswin)是博弈论中很重要的概念。
我们可以通过状态间的转移来进行推理当前状态究竟是 N N N还是 P P P,以一个简单的Bash博弈例子为例:
有一堆石子数量为 n n n,每次可以取 1 − m 1-m 1−m个石子,双方轮流取,直到不能取者输,那么问你是否当前先手必胜?
这个问题的经典做法是从最终结果反推,我们记 ( n u m b e r , s t a t u s ) (number, status) (number,status)为当前有 n u m b e r number number个石子,先手处于 N / P N/P N/P状态。我们从状态 ( 0 , P ) (0, P) (0,P)开始反推,可以知道 ( 1 ) (1) (1)到 ( m ) (m) (m)一定是状态N,即 ( 1 , N ) , . . . , ( m , N ) (1, N),..., (m, N) (1,N),...,(m,N),而 ( m + 1 ) (m+1) (m+1)一定是状态 P P P,因为在该状态下,无论怎么取,下一步到达的一定是状态 N N N,即另一方先手必赢。
于是参考这个思路,我们可以看出当前数量为 n n n时,我们只需要查看( n − 1 n-1 n−1),( n − 2 n-2 n−2),…,( n − m n-m n−m)的状态是否都是状态先手必赢( N N N)即可,若全是则当前一定为必输状态 P P P;否则,则说明一定存在一个下一步状态为必输( P P P),那么我们只需要选择这种转移方式,即可让对面面临一个先手必输( P P P)的局势。
而 S G SG SG函数的定义,就是 S G ( x ) = S G ( S G ( y 1 ) , S G ( y 2 ) , . . . , S G ( y k ) ) SG(x) = SG(SG(y_1), SG(y_2),...,SG(y_k)) SG(x)=SG(SG(y1),SG(y2),...,SG(yk)),其中 y i y_i yi代表 x x x做一步操作后能转移到的状态。
S G SG SG函数的特点时, S G ( x ) = 0 SG(x)=0 SG(x)=0时先手必败。
class Game{
private:
vector<int>cando;//cando数组为当前能做的所有操作,即每次可以取cando[i]个
vector<int>SG;
int n_;
public:
Game(vector<int>cando_, int n){
cando = cando_;
SG = vector<int>(n, -1);
}
int getSG(int x){ // 记忆化搜索SG的值
if(SG[x] != -1)return SG[x];
vector<bool>vis(n_ + 1, false);
for(int i = 0; i < vis.size();i++)vis[i] = false;
for(int v : cando){
if(x - v >=0)vis[getSG(x - v)] = true;
}
for(int i = 0; ; i++){
if(!vis[i])return SG[x] = i;
}
}
bool nowwin(int x){
return getSG(x) != 0; // SG值为0则先手必败,否则先手必胜
}
};