sg函数(博弈论)

本文深入探讨了博弈论中的必胜点和必败点概念,介绍了 nim 游戏及其异或和(nim 和)的计算。通过sg函数解析了游戏状态的胜负判断,并提供了单个游戏的解题策略,包括必胜点和必败点的分析以及sg函数的计算方法。此外,通过实例解析了如何利用sg函数解决实际问题,如欧几里得游戏和取石子游戏。文章还强调了博弈论中找必败点的重要性,并给出了相关代码实现。
摘要由CSDN通过智能技术生成

必败点(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值