博弈论

公平博弈

一般来说,OI 谈的都是这个。

定义

在任意确定状态下,所有参与者可选择的行动完全相同,仅取决于当前状态,与身份无关。

但象棋、围棋等都是非公平博弈

模型

有向图游戏

给定一个有向图,起点放一枚棋子,每次一个玩家棋子沿一条边走一步,谁先不可走则输。

  • 先看 DAG 情况:

    可以用 DP 解决:

    \(dp_u\) 的值 \(1\) 表示先手必胜, \(0\) 表示先手必败。

    则有 \(dp_u = 1 \iff \exists v (u \to v) dp_v=0\)

    \(dp_u = 0\) 同理。

    则有 \(dp_u = !(dp_{v1} \& dp_{v2} \& \cdots)\)

  • 对于一般图

    存在一种情况,叫做 和棋

    \(dp_u\) 的值 \(1\) 表示先手必胜, \(-1\) 表示先手必败,\(0\) 表示和棋。

    1. \(u\to i, \exists i,dp_i=-1 \iff dp_u=1\)
    2. \(u\to i, \forall i,dp_i=1 \iff dp_u=-1\)
    3. \(u\to i, \forall i,dp_i=1/0 且不全为 0\iff dp_u=0\)

    接下来,处理环的情况

    对于能判断的直接推,不能推的就放过。

例题

P6560 [SBCOI2020] 时光的流逝

就是板子。

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;

int n, m, q;
int in[N], ind[N];
vector<int> e[N];
int ans[N];
bool vis[N];

int main(){
	scanf("%d%d%d", &n, &m, &q);
	for(int i = 1, u, v; i <= m; i ++) scanf("%d%d", &u, &v), e[v].emplace_back(u), ind[u] ++; // 建反图
	while(q --){
		int s, t; scanf("%d%d", &s, &t);
		memset(ans, 0, sizeof ans);
		memset(vis, false, sizeof vis);
		ans[t] = -1;
		for(int i = 1; i <= n; i ++) in[i] = ind[i];
		queue<int> q; q.emplace(t);
		for(int i = 1; i <= n; i ++) if(in[i] == 0 && i != t) ans[i] = -1, q.emplace(i); // 拓扑板子
		while(!q.empty()){
			int u = q.front(); q.pop();
			if(vis[u]) continue;
			vis[u] = 1; // 拓扑板子
			if(ans[u] == -1) for(auto v : e[u]) ans[v] = 1, q.emplace(v); // 针对必败到必胜
			else { // 对于必败,到所有点都必胜,则必败
				for(auto v : e[u]){
					in[v] --;
					if(in[v] == 0) ans[v] = -1, q.emplace(v); // 已经确认所有点都已确定状态
				}
			}
		}
		printf("%d\n", ans[s]);
	}
	return 0;
} 

棋类

局面有限,并且当前局面可以转化为确定的下一个局面则可以转移。

将当前局面和哪方先手打包成一个二元组,就转化成了一个有向图游戏。

Nim 游戏

有一堆石子,每次可以取任意一堆任意多石子(不能不取),谁不能取谁输。

先放结论:

\(S = a_1 \oplus a_2 \oplus \cdots\),则若 \(S = 0\) 先手必败,否则先手必胜。

证明:

  • 先手必败 \(\iff \forall\) 后继有先手必胜;

  • 先手必胜 \(\iff \exists\) 后继有先手必败。

CODE:

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;

int n;
int a[N];

int main(){
    int t = 0; cin>>t;
    while(t --){
        int ans = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &a[i]);
            ans ^= a[i];
        }
        puts(ans == 0 ? "No" : "Yes");
    }
    return 0;
}

用途

有向图游戏的和

让我们只讨论 DAG

定义 \(SG_u = mex\{SG_v, u\to v\}\)

则我们仿照 Nim 游戏:

计算 \(X = \oplus SG_u\),若 \(x = 0\) 则先手必败,否则先手必胜,其中 \(u\) 为所有有向图游戏的起点。

由此得到推论:

\(n\) 个相同的图,若 \(n\) 为奇数,则与单图情况一样,若 \(n\) 为偶数,则先手必败。

  • SG 的证明:

    \(SG = x\)。则可以转移到 \(\forall y\) 类状态 \((y < x)\)

    其中 \(x\) 类状态表示最坏 \(x\) 步游戏结束。

    那么这个 \(SG\) 就和 Nim 游戏一样。

    故上述结论成立。

posted @ 2026-07-13 10:11  Hty111  阅读(7)  评论(0)    收藏  举报