博弈论

基础概念

  • 有向图游戏

给定一个有向无环图,图中只有一个起点,在起点上放一个棋子,两个玩家轮流沿着有向边推动棋子,每次走一步,不能走的玩家失败。

  • mex(minimum exclusion)运算

mex(S)为不属于集合 S 中的最小非负整数, \(mex(S) = \min{\{ x | x \in N, x \notin S \}}\)
例如,\(mex{\{0,1,2\}} = 3,mex{\{1,2\}} = 0\)

  • SG函数

设状态(节点)x 有 k 个后继状态(子节点)\(y_1,y_2,...,y_k\)\(SG(x) = mex(\{SG(y_1),SG(y_2),...,SG(y_k) \})\)
求有向无环图的 sg 函数可以利用记忆化搜索优化

  • SG定理

由 n 个有向图游戏组成的组合游戏,设起点分别为$s_1,s_2,...,s_n $,当 $SG(s_1) \oplus SG(s_2) \oplus ... \oplus SG(s_n) \neq 0 $时,先手必胜;反之先手必败

Nim Game

题目:有 n 堆石子,数量分别为\(a_1,a_2,...,a_n\),两个玩家均足够聪明,轮流拿石子,每次仅可以从任意一堆中拿走任意数量的石子。
结论:当\(a_1⊕a_2⊕...⊕a_n≠0\)时,先手必胜;否则先手必败。
而且,令\(a_1⊕a_2⊕...⊕a_n=x\),则定有一个数\(a_i\),有\(a_i⊕x<a_i\),则先手仅需将第 i 堆石子取走\(a_i-a_i⊕x\)个,第 i 堆留下\(a_i⊕x\),则此时所有堆的异或为 0,下一后手面对必败局面。


相关资料

易老师整理(放个大佬的链接,比较详细)
sg函数
https://www.cnblogs.com/zwfymqz/p/8469840.html
583 Nim游戏 SG函数
https://www.cnblogs.com/dx123/p/17277281.html


例题

暑假博弈论专题训练

Nim游戏

#include <bits/stdc++.h>
using namespace std;

const int maxm = 1e2+10;
typedef long long LL;
int n,s[maxm];

void out(int a,int b){
	cout<<a<<" "<<b<<endl;
	return ;
}

void solve(){
	cin>>n;
	int rr=0,c,a,b,t;
	for(int i=1;i<=n;++i){
		cin>>s[i];
		rr^=s[i];
	}
	if(rr==0){
		cout<<"2"<<endl;
		cin>>a>>b;
		rr^=s[a];
		s[a]-=b;
		rr^=s[a];
	}
	else{
		cout<<"1"<<endl;
	}
	while(1){
		for(int i=1;i<=n;++i){
			if((s[i]^rr)<s[i]){
				out(i,s[i]-(s[i]^rr));
				t=s[i];
				s[i]=s[i]^rr;
				rr^=t;
				rr^=s[i];
				break;
			}
		}
		cin>>c;
		if(c==0) return ;
		else if(c==-1){
			cout<<"0 0"<<endl;
			return ;
		}
		cin>>a>>b;
		rr^=s[a];
		s[a]-=b;
		rr^=s[a];
	}
	return ;
}

int main()
{
	int _=1;
	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}

SG函数

简单的利用 sg 函数和 sg 定理

题意
给定一个有 𝑛 个节点和 𝑚 条边的有向无环图,𝑘 个棋子所在的节点编号。
两名玩家交替移动棋子,每次只能将任意一颗棋子沿有向边移到另一个点,无法移动者视为失败。
如果两人都采用最优策略,问先手是否必胜。

思路
每个棋子都是孤立的,𝑘 个棋子拆分成 𝑘 个有向图游戏,利用 SG 定理判断即可

关键代码如下:

const int maxm = 2e3+5, inf = 0x3f3f3f3f, mod = 998244353;
vector<int> e[maxm], q(maxm, -1);

int sg(int x){//求sg函数
	if(q[x] != -1) return q[x];
	set<int> s;
	for(auto c : e[x]){
		s.insert(sg(c));
	}
	for(int i = 0; 1 ; ++ i){
		if(!s.count(i)) return q[x] = i;
	}
}

void solve(){
	int n, m, k;
	cin >> n >> m >> k;
	for(int i = 0; i < m; ++ i){
		int x, y;
		cin >> x >> y;
		e[x].push_back(y);
	}
	int ans = 0;
	for(int i = 0; i < k; ++ i){
		int x;
		cin >> x;
		ans ^= sg(x);
	}
	if(ans == 0) puts("lose");
	else puts("win");
	return ;
}

题意
给定一张 𝑛×𝑚 的矩形网格纸,两名玩家轮流行动。在每一次行动中,可以任选一张矩形网格纸,沿着某一行或某一列的格线,把它剪成两部分。首先剪出 1×1 的格纸的玩家获胜。
如果两人都采用最优策略,问先手是否必胜。
2≤𝑛,𝑚≤200
思路
双手选手足够聪明,所以他们将 1*? 的局面随意剪出来,除非在不得已的情况下。仍利用sg函数解决这个问题。可以轻易知道对于一个 w * h 的矩形纸片,玩家在一般情况下只会剪出两种形状:([w, 2] ~ [w,h - 2]) 和 ([2, h] ~ [w - 2, h]) 故我们定义二维的 sg 函数,一个纸片剪成两张的纸片的 sg 异或和为对应操作的 sg 值,再取 mex 即得当前 w,h 的 sg 值。

下为关键代码

const int maxm = 2e2 + 5, inf = 0x3f3f3f3f, mod = 998244353;
int sg[maxm][maxm];

int mex(int x, int y){
	set<int> s;
	for(int i = 2; i <= x - 2; ++ i){
		s.insert(sg[i][y] ^ sg[x-i][y]);
	}
	for(int i = 2; i <= y - 2; ++ i){
		s.insert(sg[x][y - i] ^ sg[x][i]);
	}
	for(int i = 0; 1; ++ i)
		if(!s.count(i)) return i;
}

void pre_sg(){
	for(int i = 2; i < maxm; ++ i){
		for(int j = 2; j <= i; ++ j){
			sg[i][j] = sg[j][i] = mex(i, j);
		}
	}
	return ;
}

void solve(){
	pre_sg();
	int w, h;
	while(cin >> w >> h){
		if(sg[w][h] == 0) puts("LOSE");
		else puts("WIN");
	}
	return ;
}

集合型 Nim 游戏

sg定理的运用

题意
给定 𝑚 个整数组成的集合 𝑎𝑖,给定 𝑛 堆石子的数量 𝑏𝑖。
两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须是集合 𝑎 中的整数,最后无法进行操作的人视为失败。
如果两人都采用最优策略,问先手是否必胜。
思路
每堆石子都是孤立的,把 𝑛 堆石子看做 𝑛 个有向图游戏,运用 sg 函数和 sg 定理即可求解

关键代码如下:

//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long

inline ll read(){
	ll x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
	return x*f;
}

using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*

*/
const int maxm = 1e4+5, inf = 0x3f3f3f3f, mod = 998244353;
int n, m, k, q[maxm], sg[maxm], s[maxm], vis[maxm];

void pre(){//预处理sg值
	mem(sg, 0);
	for(int i = 1; i <= maxm; ++ i){
		mem(vis, 0);
		for(int j = 0; j < k; ++ j){
			if(s[j] > i) break;
			vis[sg[i - s[j]]] = 1;
		}
		for(int j = 0; 1 ; ++ j){
			if(vis[j] == 0){
				sg[i] = j;
				break;
			}
		}
	}
	return ;
}

void solve(){
	while(cin >> k){
		if(k == 0) break;
		for(int i = 0; i < k; ++ i) cin >> s[i];
		sort(s, s + k);
		pre();
		int val, t;
		string ans = "";
		cin >> m;
		while(m--){
			val = 0;
			cin >> n;
			for(int i = 0; i < n; ++ i){
				cin >> t;
				val ^= sg[t];
			}
			if(val == 0) ans += 'L';
			else ans += 'W';
		}
		cout << ans << '\n';
	}
	return ;
}

signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _=1;
//	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}
posted on 2023-05-07 20:51  Qiansui  阅读(30)  评论(0编辑  收藏  举报