必败点(P点):此点无法操作(或会走到无法操作的点)
必胜点(N点):此点操作可取胜(或会走到最后一个可操作的点)
nim和(异或和):各个数相异或的结果
母游戏的sg:
sg(a)=sg(b) ^ sg( c) ^ sg( d) 即子游戏的异或和
a是母游戏,b和c以及d是a的子游戏
ag(a)==0,则先手必败,!=0则先手必胜
(结论记住就好,想知道原理,去了解nim博弈结论的推理过程)
子游戏的sg:
sg(x)=mex( sg(y) | y是x的后继 )
sg(y)==0时,说明y在(到达了)终点
P点(只能进入) ⇒ N点 (Alice ⇒ Bob)
N点(至少有一种方法(即存在)进入)⇒ P点 (Bob ⇒ Alice)
博弈论的本质:
必胜点和必败点之间的转换
即结果一开始便注定了
所以找必败点便成了解题关键
所以所有点一开始便确定了其是否为必败点
博弈论也可以看成棋子在有向无环图的移动
无向图的终点无法移动(即无法操作)即其为必败点,所以其sg=0
无向图的次终点可将下一步转为必败点,所以其为必胜点,sg=1
所以
必败点(sg=0):一定会走到终点的点
必胜点(sg>0):一定会走到次终点的点
无向图单链的话(只有一种操作),那sg只有0和1
但实际问题中,一般都是多链
注:状态和状态的sg不一样
nim博弈是刚好状态等于状态的sg,是特例
int sg[N],vis[N],op[M]; //op[i]表示第i种操作(取i个石子) //设op[]={x,2,3,4}
void getsg(int n){ //求1~n的sg值 //求解过程有点像递推(dp),sg[0]=0(初始边界)(想一下即可得)
for(int i=1;i<=n;++i){ //sg[i]==0时(无法操作时)(无法按规则取石子)的状态即必败态,所以此题0,1必败态,所以此题的i可以从2开始,但从1开始也没影响
memset(vis,0,sizeof vis);
for(int j=1;op[j]<=i&&j<=m;++j){
vis[ sg[i-op[j]] ]=1; //i-op[j]表示i可进行的下一步操作即i的后继 (所以i-op[j]不能为负,比如i==1,op[j]==3时:不能从一个石子中取出3个石子)
}
for(int j=0;;++j){
if(!vis[j]){
sg[i]==j;break;
}
}
}
}
博弈论通解:
单个游戏:找必胜态和必败态(也可以用sg函数)
组和游戏:求sg函数,再用sg定理
单个游戏:
既可以找必胜态和必败态直接输出答案,也可以求sg函数是否为0
分析要点:
1、分析必胜点和必败点都是以终结点进行逆序分析
2、奇偶性
3、从最简单的必胜态(必败态)入手
以下为例题:
单个游戏的sg函数解法:
P1290 欧几里德的游戏
参考博客
sg==0 必败(必败态)
sg!=0 必胜(必胜态)(为了简便其 !=0 都可以看作 ==1)
看到一个数减另一个数的任意倍数,
就要想到 %(取模)
若还要重复操作,就要想到gcd
当n==0 || m == 0 必败
设n>m
则sg(n,m) =mex{ sg(n-m,m) , sg(n-2m,m) …sg(n%m+m,m) , sg(m,n%m) } (多链图)
sg(n,m)后继所有状态都包含sg(m,n%m)状态,则称sg(m,n%m)为交汇点【sg(n,m)的后继都会交汇在此点】
当 sg(m,n%m)=0时,sg(n%m+m,m)为1,其余为2,3,4,5…它们都可以看成1
当sg(m,n%m)=1时,sg(n%m+m,m)为0,其余为2,3,4,5…它们都可以看成1
则此题只要判断sg(m,n%m)和sg(n%m+m,m)即可
代码用gcd的写法
#include<bits/stdc++.h>
using namespace std;
int n,m,t,fg;
int pan(int x,int y){
if(y==0) return 0;
if(x/y==1) return !pan(y,x%y);
else return 1;
}
int main(){
cin >> t;
while(t--){
cin >> n >> m;
fg=pan(max(n,m),min(n,m));
if(fg) cout << "Stan wins\n";
else cout << "Ollie wins\n";
}
return 0;
}
单个游戏分析必胜点和必败点:
P4018 Roy&October之取石子
博弈论其实都是规律题
解法一(从终点分析必败态和必胜态):
1 必胜
2 必胜
3 必胜
4 必胜
5 必胜
6 必败
7 必胜
8 必胜
9 必胜
10 必胜
11 必胜
12 必败
13 必胜
14 必胜
15 必胜
观察可得6的倍数为必败态,其余为必胜态
解法二(sg函数打表):
sg函数本质:
1、定义0为必败态
2、定义sg[0]=0 即 sg[0]为必败态(既可以用边界理解,也可以用无法操作理解)
每次操作都希望给对手一个必败态(因为绝顶聪明,希望自己赢,所以找必败态给对手)
所以每次mex操作就是在此节点的后继找出必败态给对手
sg!=0就是在后继找得到必败态 即其后继存在sg=0
sg==0就是在后继找不到必败态 即其后继不存在sg=0
所以局面状态和sg函数就是一个东西
所以博弈论得关键在于找必败态
sg函数打表也打必败态的表(找必败态)
单个游戏三种解法
1、先以分析必败态
2、分析sg函数
3、sg函数打表(就是博弈论的打表)
#include<bits/stdc++.h>
using namespace std;
int const N=1e3+7;
int n,m,fg,cnt;
int vis[N],p[N],t[N];
vector<int>v;
void shai(){
for(int i=2;i<=n;++i){
if(!vis[i]) {
vis[i]=i;
p[++cnt]=i;
int j=i;
while(j<=n) t[j]=1,j*=i;
}
for(int j=1;j<=cnt&&p[j]<=vis[i]&&p[j]*i<=n;++j){
vis[ p[j]*i ]=p[j];
}
}
v.push_back(1);
for(int i=2;i<=n;++i) if(t[i]) v.push_back(i);
}
int sg[N];
void solve(int n){
sg[0]=0;
for(int i=1;i<=n;++i){
memset(vis,0,sizeof vis);
for(int j=0;v[j]<=i&&j<v.size();++j){
vis[ sg[i-v[j]] ]=1;
}
for(int j=0;;++j){
if(!vis[j]) {
sg[i]=j;break;
}
}
cout << i << " " << sg[i] << "\n";
}
}
int main(){
cin >> n;
shai();
solve(n);
return 0;
}
以下为ac代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,t,fg;
int main(){
cin >> t;
while(t--){
cin >> n;
if(n%6) cout << "October wins!\n";
else cout << "Roy wins!\n";
}
return 0;
}