Loading

博弈论

博弈论学习笔记

基本模型

对于一般博弈论,可以看 APIO2025 刘恒熙的授课《博弈理论入门》。这类游戏在 OI 题中很少出现。但是这东西很强,可以推出公平组合游戏的 SG 函数。

当左右决策不同时,我们将整个博弈看作一棵树。

对于公平组合游戏,将整个博弈过程看作一张DAG。

image-20250627100838092

image-20250627101323710

image-20250627190010377

对于任何一个无偏游戏,我们可以通过写下其后继状态来递归确定这个游戏。设 \(x\) 的后继状态为 \(y_1\sim y_k\),则 \(x=\{y_1,y_2,\cdots,y_k\}\)

定义多个游戏的和,表示每次在多个游戏中随便选择一个游戏走一步,知道最后一方不能在任何游戏中行走时判负。

形式化的,游戏 \(x_1\) 与游戏 \(x_2\) 的和定义为:

\[x_1+x_2=\{x_1+y_{21},x_1+y_{22}+\cdots +x_1+y_{2k},y_{11}+x_2,y_{12}+x_2,y_{1K}+x_2\} \]

这是一个递归定义,\(y_{1*}\) 表示 \(x_1\) 的后继游戏,\(y_{2*}\) 表示 \(x_2\) 的后继游戏。

如果两个局面 \(x,y\) 在博弈中行为完全相同,则记作 \(x=y\)

Nim 博弈

\(n\) 个石子堆,第 \(i\) 个石子堆有 \(a_i\) 个,每次从一个石子堆中拿走至少一个石子,无法行动者负。

结论:当且仅当当 \(\bigoplus a_i=0\) 时先手必败。

证明分为三个步骤:

  1. \(\forall i, a_i=0\) 时,根据定义先手必败,满足结论。

  2. \(\bigoplus a_i\ne 0\) 一定存在某种移动使得 \(\bigoplus a_i=0\)

    假设 \(\bigoplus a_i=k,k\ne 0\),则我们要把一个状态 \(a_i\) 改作 \(a_i'\) 使得 \(k\oplus a_i\oplus a_i'=0\)

    假设 \(k\) 的最高位为 \(d\),那么一定存在某个 \(a_i\) 二进制下第 \(d\) 位为 \(1\),而 \(a'=a_i\oplus k\) 这一位为 \(0\)

    所以 \(a_i>a_i'\),移动合法。

  3. \(\oplus a_i=0\) 一定不存在某种移动使得 \(\oplus a_i =0\)

    假设 \(a_i\to a_i'\) 使得 \(\oplus a_i=0\) ,则 \(a_i\oplus a_i'=0\implies a_i=a_i'\),移动不合法。

我们将”有 \(n\) 个石子的一个石子堆“这个状态记作 \(*n\),显然有 \(*n=\{*0,*1,\cdots ,*(n-1)\}\)。称作 nimber。

那么一个 Nim 博弈记作 \(\sum_{i=1}^n*a_i\)

SG 函数

对于游戏 \(x=\{y_1,y_2,\cdots,y_k\}\) 定义 \(SG\) 函数:

\[SG(x)=\operatorname{mex}\limits_{i=1}^kSG(y_k) \]

\(x\) 无后继,则先手必败,\(SG(x)=0\)

用 SG 函数表示 Nimber

很明显有 \(SG(*0)=0\)\(SG(*n)=\operatorname{mex}_{i=0}^{n-1}SG(*i)=n\)

SG 定理

\(SG(x)=y\),则 \(x=*y\)

证明:

\(x=\{y_1,\cdots y_k\}\),假设对于 \(\forall i,y_i\) 都满足 SG 定理,现在证明 \(x\) 也满足 SG 定理。

首先如果 \(x\) 走到了一个 \(y_i\) 使得 \(SG(y_i)>SG(x)\) 那么 \(y_i\) 一定可以再走到一个 \(z\) 使得 \(SG(z)=SG(x)\),根据”化简博弈:绕开可逆行动",这样的行动时无意义的,我们可以直接删去。

于是 \(x=\{*0,*1,\cdots,*(SG(x)-1)\}=*SG(x)\)

这样我们可以通过 \(nimber\) 的性质研究任何博弈的性质。

巴什博弈

一堆石子,共 \(n\) 个两人轮流取走 \([1,m]\) 个石子,无法行动者负。

结论:若 \((m+1)|n\),则先手必败,否则先手必胜。

证明:直接使用 SG 函数:设还剩 \(x\) 个石子的局面为 \(X\)。则:

\[SG(N)=\operatorname{mex}_{i=n-m}^{i-1}SG(I) \]

很明显,当 \((m+1)|n\)\(SG(N)=0\)

阶梯 Nim

\(n\) 堆石子,每次选择一个 \(i\ge 1\),从 \(a_{i+1}\) 移动至少 \(1\) 个石子到 \(a_i\),无法行动者负。

结论:当且仅当 \(\bigoplus_{i=1}^{\lfloor\frac n2\rfloor}a_{2i}=0\),即偶数位置异或和为 \(0\) 时,先手必败。

简要证明:因为不论从奇数堆想偶数堆移动多少,下一手总是可以移动走,回到一个等价的状态,根据”化简博弈:绕开可逆行动",这样的行动时无意义的,我们可以直接删去。

于是就等价于在偶数位置上玩 Nim 博弈。

当然可以用 SG 函数严谨证明,即证明 \(SG(\{a_i\})=\bigoplus_{i=1}^{\lfloor\frac n2\rfloor}a_{2i}\)

Anti-Nim

\(n\) 个石子堆,第 \(i\) 个石子堆有 \(a_i\) 个,每次从一个石子堆中拿走至少一个石子,无法行动者胜。

结论:设 \(k=\max_ia_i,X=\bigoplus a_i\),则先手必败当且仅当 \([k>1]\oplus [X=0]\)

K-Nim

\(n\) 个石子堆,第 \(i\) 个石子堆有 \(a_i\) 个,每次从至少 \(1\) 至多 \(k\) 个石子堆中拿走至少一个石子,无法行动者负。

结论:设每个数第 \(i\) 位的 \(1\) 的个数之和为 \(b_i\),先手必败当且仅当 \(\forall i,(k+1)|b_i\)

一些解题技巧

假设法

利用了一个公平组合游戏局面不是先手必胜就是先手必败,那么就可以假设一个局面是先手必胜还是先手必败,思考其决策。

例题:2024.7.9T1 在本题中,发现无论去除 1 后的局面是先手必胜还是先手必败,都可以通过相应的决策使得初始局面时先手必胜。

博弈论DP

就是从终态往前推

Game on Sum

Hard VersionEasy Version

题面

Alice 和 Bob 正在玩一个游戏,游戏分为 n 个回合,Alice 和 Bob 要轮流对一个数 x 进行操作,已知这个数初始值是 0。

具体每个回合的行动规则如下:

  1. Alice 选择一个在区间 [0,k] 之间的实数 t。
  2. Bob 可以选择让 x 变成 x+t 或者 x−t,但是 Bob 在 n 个回合之内至少选择 m 次让 x 变成 x+t。

Alice想让最终的 x 最大,Bob 想让最终的 x 最小。

已知双方均采用最优策略,求最终的 x 值(对 10^9+7 取模)。

数据范围保证:1≤m≤n≤2000,k≤10^9+7。

题解

CF1628D2 Game on Sum (Hard Version) - 洛谷专栏 (luogu.com.cn)

Alice先走,Bob后走其实就是让最小值最大。

打表看结论

并且博弈论问题如果不是用 DP,那一定是找结论。且数据范围很大时——\(\mathcal O(N)\),一定是找结论。与其观察什么时候先手必胜,不如观察什么时候先手必败,

Caught in the Middle

打表发现先手必败当且仅当 R 与 L 括号匹配。证明方法与 Nim 博弈相同,分为可以到必败态与只能到必胜态。

P6791 [SNOI2020] 取石子 - 洛谷

”还剩 \(n\) 个石子,限制为 \(k\)“的状态记作 \((n,k)\),则可以列出 \(SG\) 函数:

\[SG(n,k)=\left\{\begin{aligned}&\operatorname{mex}_{i=1}^kSG(n-i,2i)&n\ne 0\\&0&n=0\end{aligned}\right. \]

打出 \([SG(n,k)>0]\) 的表为:

image-20250627142601837

先是发现在斐波那契数处 \(0\) 非常的多,接着发现其他地方 \(0\) 的也和斐波那契数列有关,最终可以得到以下结论:

设正整数 \(n\) 拆成若干个不同的斐波那契数之和后,其中最小的为 \(\tau(n)\) 则 。

\[\forall k<\tau(n),SG(n,k)=0\\\forall k\ge \tau(n),SG(n,k)>0 \]

于是 \(ans=f(N-1)=\sum_{n=1}^{N-1}[\tau(n)\le k]\),设小于等于 \(n\) 的最大的斐波那契数为 \(x\) 则有 \(f(n)=f(x-1)+f(n-x)+[x\le k]\),计算即可,复杂度 \(\mathcal O(T\log N)\)

CF1149E Election Promises

\((x,t)\) 表示在 \(x\) 节点上,tax 为 \(t\) 的状态。

从SG函数角度考虑。首先对于一个没有任何出边的点 \(z\),其实等价于一个 Nim 石子堆,即 \(SG(z,t)=t\)

对于一个直接与叶子 \(z\) 相连的点 \(y\),有:

\[SG(y,1)=\operatorname{mex}_{t=0}^{\infin}(SG(z,t)) \]

那么 \(SG(y,1)\) 可以取到无穷大。我们设此时 \(SG(y,1)=\omega\),满足 \(\omega\) 是一个无穷大的量。

对于状态 \((y,t)\),根据定义有:

\[SG(y,t)=\operatorname{mex}_{i<t,0\le t_2}(SG((y,i)+(z,t_2))) \]

所以 \(SG(y,t)=t\times \omega=t\omega\)

对于一个直接与 \(y,z\) 相连的点 \(x\),有:

\[SG(x,1)=\operatorname{mex}_{t_1,t_2}(SG((y,t_1)+(z,t_2))) \]

那么 \(x\) 可以取到 \(\omega\)\(\omega\),于是记 \(SG(x,1)=\omega^2\),同理 \(SG(x,t)=t\omega^2\)

按照这种规则,我们可以求得每个点的SG函数,然后对其异或后求游戏和。运算规则为:

\[(\bigoplus_i a_i\omega^i)\oplus (\bigoplus_i b_i\omega^i)=(\bigoplus_i (a_i\oplus b_i)\omega^i) \]

这是因为不同 \(\omega\) 的次幂之间没有定义 \(\oplus\),所以不能计算。

设最后异或的结果为 \(\bigoplus_i a_i\omega^i\)

于是可以猜测先手必败当且仅当最后的结果 \(=0\),即每一位上的异或和都等于 \(0\)。也十分好证明:

  1. 对于停止局面,所有城市上的 tax 都等于 \(0\),成立。
  2. \(\oplus =0\) 时,无论怎么移动,一定会使得 \(\oplus \ne 0\)
  3. \(\oplus \ne 0\) 时,取满足 \(a_t\ne 0\) 的最大的 \(t\),用类似 Nim 的方式找到一个对应的点 \(x\),满足 \(SG(x,1)=\omega^t\),首先让他减少为 \(h_x\gets h_x\oplus a_t\),其次考虑每条出边,由于可以到达所有的 \(o\le t\),设 \(x\) 的出点为 \(y\)\(SG(y,1)=\omega^o\)\(h_y\gets h_y\oplus a_o\) 即可。注意 \(o\) 相同的 \(y\) 只能取一个。

代码:

#include<bits/stdc++.h>
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define int long long
#define lop(i,a,b) for(int i=a;i<=b;i++)
#define pol(i,a,b) for(int i=a;i>=b;i--)
#define fi first
#define se second
#define mset(a,v) memset(a,v,sizeof a)
#define mcpy(a,b) memset(a,b,sizeof b)
#define umap unordered_map
#define pb push_back
#define pc(x) __builtin_popcountll(x)
using namespace std;
typedef pair<int,int> pa;
typedef vector<int> vi;
const int NN=2e5+5;
int n,m,h[NN];
vector<int> ed[NN];
bool vst[NN];
bool deal[NN];
int bit[NN];//每一位的指数 
int a[NN];//异或多项式 
int mex(vi v){
	sort(v.begin(),v.end());
	int cnt=0;
	for(int o:v){
		if(o<cnt)continue;
		if(o!=cnt)return cnt;
		cnt++;
	}
	return cnt;
}
void DFS(int x){
	if(vst[x])return;
	vst[x]=1;
	vi v;
	for(int y:ed[x]){
		DFS(y);
		v.pb(bit[y]);
	}
	bit[x]=mex(v);
	return;
}
int HighBit(int x){
	x|=x>>1;
	x|=x>>2;
	x|=x>>4;
	x|=x>>8;
	x|=x>>16;
	x|=x>>32;
	return x-(x>>1); 	
}
signed main(){
	cin>>n>>m;
	lop(i,1,n)cin>>h[i];
	lop(i,1,m){
		int u,v;cin>>u>>v;
		ed[u].pb(v);
	}
	lop(x,1,n)if(!vst[x])DFS(x);
	lop(x,1,n)a[bit[x]]^=h[x];
	int d=0;
	lop(b,0,n-1)d|=a[b]>0;
	if(!d){
		cout<<"LOSE\n";
		return 0;
	}
	cout<<"WIN\n";
	//找到最高位 
	int hib=0,p=0;
	pol(b,n-1,0)if(a[b]){hib=b;break;}
	//遍历每个点,找到最高位对应的点 
	lop(x,1,n){
		if(bit[x]!=hib)continue;
		if(h[x]&HighBit(a[hib])){p=x;break;}
	}
	//找到点了 
	h[p]=h[p]^a[hib];//先减少这个节点使得当前位满足 
	for(int y:ed[p]){
		if(deal[bit[y]])continue;
		deal[bit[y]]=1;
		h[y]=a[bit[y]]^h[y];//任意改变权值,使得bit[y]为满足限制 
	}
	lop(i,1,n)cout<<h[i]<<" ";
	return 0;
}

从理解题意到想出解法再到AC,一共用时:1h29min25s。

img

posted @ 2025-01-10 20:44  lupengheyyds  阅读(38)  评论(0)    收藏  举报