博弈论基础

前置知识

  • mex ⁡ \operatorname {mex} mex:没有出现过的最小自然数,如 mex ⁡ { 0 , 2 , 3 } = 1 \operatorname {mex} \{0,2,3\}=1 mex{0,2,3}=1
  • ⊕ \oplus :按位异或。

前言

博弈类问题大致分为,公平组合游戏、非公平组合游戏(绝大多数的棋类游戏)、反常游戏。
只需要关注公平组合游戏 (ICG) \texttt{(ICG)} (ICG),反常游戏是公平组合游戏的变形,经济类博弈也不是博客所讨论的范围

  • 两个玩家轮流行动游戏方式一致
  • 两个玩家对状况完全了解
  • 游戏一定会在有限步数内分出胜负
  • 游戏以玩家无法行动结束

博弈的双方都被认为是神之个体,因为所有玩家对状况完全了解,且充分为自己打算,绝对理性。
当局面确定,结果必然注定,并且没有任何随机的成分。
游戏中的每一个状态,最终导致的结果也必然注定,只有必胜态、必败态,两种状态。
这一类博弈问题的结果没有任何意外,一方可以通过努力去改变结果是不可能的,这一点是反直觉的。

  • 常用对数器打表来找规律。
  • 博弈论的题目就是 要干去干,去找规律,不要怕

巴什博弈 (Bash) \texttt{(Bash)} (Bash)

n n n 个的石头,每次操作可以拿走 [ 1 , m ] [1,m] [1,m] 个石头。拿到最后 1 1 1 个石头的人获胜(也就是不能拿石头的人输)。

这个问题特别简单,就是显然答案是如果 n n n m + 1 m+1 m+1 的倍数那么先手输,否则先手赢。

sg \texttt{sg} sg 函数

接下来引入 sg \texttt{sg} sg 函数。
sg \texttt{sg} sg 表示当前状态的胜负情况。
有如下公式 s g ( A ) = mex ⁡ ( s g ( B ) ∣ A → B ) sg(A)=\operatorname {mex} (sg(B)|A\to B) sg(A)=mex(sg(B)AB)
式子中的 A A A B B B 表示状态, A → B A\to B AB 表示 A A A 状态可以达到 B B B状态。也就是说我们可以通过 A A A 能达到的状态的SG函数值推算出 A A A S G SG SG 值。
如果 sg \texttt{sg} sg 值为 0 0 0 则表示先手输,否则先手赢。

数学归纳法证明:

  1. 最终状态 S G = 0 SG=0 SG=0
  2. 对于任意一个必胜态 S G ≠ 0 SG\not=0 SG=0,存在一个后继态 S G = 0 SG=0 SG=0
  3. 对于任意一个必败态 S G = 0 SG=0 SG=0,不存在一个后继态 S G = 0 SG=0 SG=0

e.g. \texttt{e.g.} e.g.:在巴什博奕中:当 m = 2 m = 2 m=2 时, sg[0]=0,sg[1]=1,sg[2],   sg[3]=0, ⋯ \texttt{sg[0]=0,sg[1]=1,sg[2], sg[3]=0,}\cdots sg[0]=0,sg[1]=1,sg[2], sg[3]=0,
FAQ \texttt{FAQ} FAQ:

  • Q: 不就是判断一个输赢吗?为什么要用一个 int \texttt {int} int,我 bool \texttt{bool} bool 也可以啊。
  • A: 是的,确实可以只有 bool \texttt{bool} bool,用 int \texttt{int} int 另有用途,我们下面会讲。

P2197 【模板】 (Nim) \texttt{(Nim)} (Nim) 游戏—— sg \texttt{sg} sg 多个独立的子问题的合并

题目描述

甲,乙两个人玩 nim \texttt{nim} nim 取石子游戏。
nim \texttt{nim} nim 游戏的规则是这样的:地上有 n n n 堆石子(每堆石子数量小于 1 0 4 10^4 104),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这 n n n 堆石子的数量,他想知道是否存在先手必胜的策略。

公式引入

若局面X由若干个子游戏 X 1 , X 2... X n X1,X2...Xn X1,X2...Xn 构成,则
S G ( x ) = S G ( X 1 ) ⊕ S G ( X 2 ) ⊕ ⋯ ⊕ S G ( X n ) SG(x)=SG(X_1)\oplus SG(X_2) \oplus \cdots\oplus SG(X_n) SG(x)=SG(X1)SG(X2)SG(Xn)
数学归纳法证明:

  1. 最终局面成立
  2. ∀ X \forall X X,所有后继局面可以取到 G ( X 1 ) ⊕ S G ( X 2 ) ⊕ ⋯ ⊕ S G ( X n ) − 1 G(X_1)\oplus SG(X_2) \oplus \cdots\oplus SG(X_n)-1 G(X1)SG(X2)SG(Xn)1,取不到 S G ( X 1 ) ⊕ S G ( X 2 ) ⊕ ⋯ ⊕ S G ( X n ) SG(X_1)\oplus SG(X_2) \oplus \cdots \oplus SG(X_n) SG(X1)SG(X2)SG(Xn)
    同样看做 Nim \texttt{Nim} Nim 游戏
    S → S 1 S \rightarrow S_1 SS1
    S ⊕ ( S ⊕ S 1 ) = S 1 S \oplus (S \oplus S_1) =S_1 S(SS1)=S1
    ( S ⊕ S 1 ) (S \oplus S_1) (SS1) 最高位为 k k k,存在 A A A 使得 k k k 位为 1 1 1
    那么 A ⊕ ( S ⊕ S 1 ) < A A \oplus (S \oplus S_1) < A A(SS1)<A,所以让 A A A 变成 A ⊕ ( S ⊕ S 1 ) A \oplus (S \oplus S_1) A(SS1)就行了。

这就是 SG \texttt{SG} SG 定理( Bouton \texttt{Bouton} Bouton 定理)。

问题解答

显然对于每一个子问题,对于石头个数为 n n n sg(n)=n \texttt {sg(n)=n} sg(n)=n

所以 S G = ⊕ i = 1 n a i SG=\oplus_{i=1}^{n}a_i SG=i=1nai。判断 ⊕ i = 1 n a i 是否为零即可 \oplus_{i=1}^{n}a_i 是否为零即可 i=1nai是否为零即可

代码

#include<bits/stdc++.h>
using namespace std;
int t, n, a;
signed main(){
	scanf("%d", &t);
	while (t--) {
		scanf ("%d", &n); int ans = 0;
		while (n--) scanf("%d", &a), ans ^= a;
		if (ans == 0) puts("No"); else puts("Yes");
	}
    return 0;
}

P4279 [SHOI2008] 小约翰的游戏

题目描述

甲,乙两个人玩 取石子游戏。
游戏的规则是这样的:地上有 n n n 堆石子(每堆石子数量小于 5000 5000 5000),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就====了。假如甲是先手,且告诉你这 n n n 堆石子的数量,他想知道是否存在先手必胜的策略。
这一题唯一的区别是 最后没石子可取的人就赢了

题目解析

首先,如果 ∀ i \forall i i a i = 1 a_i=1 ai=1,那么就是看 n n n 的奇偶性了。
其次,如果只有一个 a i ≠ 1 a_i\not=1 ai=1,那么先手必赢 为什么
最后,因为要的是“只有一个 a i ≠ 1 a_i\not=1 ai=1”,异或和不为 0 0 0, 所以只要抓住异或和不为 0 0 0 即可获胜,那么就转化为 (Nim) \texttt{(Nim)} (Nim) 游戏了。

示例代码

#include<bits/stdc++.h>
using namespace std;
int main() {
    int t, n, x, ans, sum;
    scanf("%d", &t);
    while (t--) {
        cin >> n, ans = sum = 0;
        for(int i = 1; i <= n; ++ i) scanf("%d", &x), ans ^= x, sum += x;
        if(sum == n) puts(n & 1 ? "Brother" : "John");
        else puts(ans ? "John" : "Brother");
    }
	return 0;
}

P6487 [COCI2010-2011#4] HRPA ——斐波那契博弈

前置知识

  1. 斐波拉契数列:
    F = { 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , . . . . . } F ( n ) = { 0 if  n = 0 1 if  n = 1 F ( n − 1 ) + F ( n − 2 ) if  n > 1 F = \{0,1,1,2,3,5,8,13,21,34,.....\}\\ F(n) = \begin{cases} 0 & \text{if } n = 0 \\ 1 & \text{if } n = 1 \\ F(n-1) + F(n-2) & \text{if } n > 1 \end{cases} F={0,1,1,2,3,5,8,13,21,34,.....}F(n)= 01F(n1)+F(n2)if n=0if n=1if n>1
  2. 齐肯多夫(Zeckendorf)定理:任何正整数都可以表示成若干个不连续的斐波那契数之和。如 4 = 1 + 3 ( 1 4=1+3(1 4=1+3(1 3 ) 3) 3) 不相邻。

证明:

  • 若正整数 n n n 为斐波那契数,得证。
  • 否则,先取 $ F_{t_1} $,其中 $ t_1 $ 满足 $ F_{t_1} < n < F_{t_1 + 1} $。
    • 令 $ n’ = n - F_{t_1} $,同上一步取出一个 $ F_{t_2} $ 满足 $ F_{t_2} < n’ < F_{t_2 + 1} $。
    • 只要证明 $ t_1 \neq t_2 + 1 $。考虑反证法:
      • 假设 $ t_1 = t_2 + 1 $,则第一步取出的应当是 $ t_1 + 1 $ 而不是 $ t_1 $。原因是 $ F_{t_1 + 1} = F_{t_1} + F_{t_1 - 1} $。

题目分析

如果 t t t 为斐波拉契数,那么必须全部取完。

设 $ n = F_{i b_t} $,我们把 $ n $ 看成 $ F_{i b_{t-1}} $ 和 $ F_{i b_{t-2}} $ 两堆。

  • 若第一步取的个数超过 $ F_{i b_{t-2}} $,则后手可以直接取完剩余石子。
  • 否则,该问题变成了一个规模更小的同样的问题。

考虑 $ n = 2 $ 的情况(即规模最小的情况),先手只能取 1,于是后手取 1获胜。

可以结合下面理解一下

比如 43 = 34 + 8 + 1 43=34+8+1 43=34+8+1 那么答案为 1 1 1
首先取走 1 1 1,然后如果对方选择的数只能选择 [ 1 , 2 ] [1,2] [1,2]
假如对方取的是 2 2 2,那么 8 = 3 + 5 8=3+5 8=3+5,我选择把 3 3 3 消掉,我就选择 1 1 1
这时数字变成了 34 + 5 34+5 34+5
对方又只能选择 [ 1 , 2 ] [1,2] [1,2], 假如他选择 1 1 1,那么 5 = 2 + 3 5=2+3 5=2+3,我就把 2 2 2消掉,选择 1 1 1
这时数字变成 34 + 3 34+3 34+3
对方又只能选择 [ 1 , 2 ] [1,2] [1,2], 假如他选择 2 2 2,那么我就把 3 3 3消掉,选择 1 1 1
这时数字变成 34 34 34
对方又只能选择 [ 1 , 2 ] [1,2] [1,2], 假如他选择 2 2 2,那么 34 = 21 + 8 + 5 34=21+8+5 34=21+8+5消掉,我就把 5 5 5 消掉,选择 3 3 3
这时数字变成 21 + 8 21+8 21+8
对方又只能选择 [ 1 , 6 ] [1,6] [1,6], 假如他选择 6 6 6,那么 29 = 21 + 8 29=21+8 29=21+8消掉,我就把 8 8 8 消掉,选择 2 2 2
这时数字变成 21 21 21
对方又只能选择 [ 1 , 6 ] [1,6] [1,6], 假如他选择 6 6 6,那么 29 = 21 + 8 29=21+8 29=21+8消掉,我就把 8 8 8 消掉,选择 2 2 2
这时数字变成 21 21 21
对方又只能选择 [ 1 , 4 ] [1,4] [1,4], 假如他选择 4 4 4,那么 21 = 13 + 8 21=13+8 21=13+8消掉,我就把 8 8 8 消掉,选择 4 4 4
这时数字变成 13 13 13
对方又只能选择 [ 1 , 8 ] [1,8] [1,8], 假如他选择 8 8 8,那么 13 = 8 + 5 13=8+5 13=8+5消掉,我就把 5 5 5 直接消掉,获得胜利。

例题

例一:两堆石头的巴什博弈

题目描述

有两堆石头,数量分别为 a a a b b b
两个人轮流拿,每次可以选择其中一堆石头,拿 [ 1 , m ] [1,m] [1,m]
拿到最后一颗石子的人获胜,根据 a a a b b b m m m 返回谁赢。

题解

根据 SG \texttt{SG} SG 定理,只要 s g [ a ] ⊕ s g [ b ] ≠ 0 sg[a] \oplus sg[b] \not = 0 sg[a]sg[b]=0 即可,即 s g [ a ] ≠ s g [ b ] sg[a] \not = sg[b] sg[a]=sg[b],即 a ≢ b ( m o d m ) a \not \equiv b \pmod{m} ab(modm)

代码

bool solve (int a, int b, int m) {return a % m != b % m; }

例二:三堆石头拿取斐波那契数博弈

题目描述

有三堆石头,数量分别为 a , b , c ( a , b , c ≤ 1 0 5 ) a,b,c(a,b,c\le 10^5) a,b,c(a,b,c105)
两个人轮流拿,每次可以选择其中一堆石头,拿取斐波那契数的石头
拿到最后一颗石子的人获胜,根据 a , b , c a,b,c a,b,c 返回谁赢。
直接分别计算 sg \texttt{sg} sg 值即可。因为 sg \texttt{sg} sg 值都只会从最多 25 25 25 个后继推出,所以 ∀ i , sg[i] ≤ 30 \forall i, \texttt{sg[i]}\le30 i,sg[i]30。时间复杂度为 O ( n m ) O(nm) O(nm)。 其中 m m m [ 1 , n ] [1,n] [1,n] 中斐波那契数的个数。

例三:P2148 [SDOI2009] E&D

题目描述

桌子上有 2 n 2n 2n 堆石子,编号为 1 , 2 , 3 ⋯   , 2 n 1,2,3\cdots,2n 1,2,3,2n
其中 1 , 2 1,2 1,2 为一组; 3 , 4 3, 4 3,4 为一组; 5 , 6 5, 6 5,6 为一组 , ⋯   , 2 n − 1 , 2 n ,\cdots, 2n-1,2n ,,2n1,2n 为一组。

每组可以进行分割操作:

  • 任取一堆石子,将其移走,然后分割同一组的另一堆石子
  • 从中取出若干个石子放在被移走的位置,组成新的一堆
  • 操作完成后,组内每堆的石子数必须保证大于 0 0 0
  • 显然,被分割的一堆的石子数至少要为 2 2 2

两个人轮流进行分割操作,如果轮到某人进行操作时,所有堆的石子数均为 1 1 1,判此人输掉比赛。

返回先手能不能获胜。

题目解析

首先 sg(x,y) \texttt{sg(x,y)} sg(x,y) 表示当前一组内石子数分别为 x , y x,y x,y 的胜负情况。
直接暴力算出 20 × 20 20\times20 20×20 sg \texttt{sg} sg 表。
找规律。
发现 s g ( x , y ) = f ( ( x − 1 ) ∣ ( y − 1 ) ) sg(x, y)=f((x-1)|(y-1)) sg(x,y)=f((x1)(y1))
其中 f ( x ) f(x) f(x) 表示 x x x 最低 0 0 0 的位置如 8 = ( 1000 ) 2 , f ( 8 ) = 0 ; 7 = ( 0111 ) 2 , f ( 7 ) = 3 8=(1000)_2,f(8)=0;7=(0111)_2,f(7)=3 8=(1000)2,f(8)=0;7=(0111)2,f(7)=3
详细证明见 题解 P2148 【[SDOI2009]E&D】
代码易得,略。

例四:CF87C Interesting Game

题目简述

每次可以把一堆石子分成若干堆,使得分裂的石子是公差为 1 1 1 的等差数列。不能分的人输。

题目分析

直接算 sg \texttt{sg} sg 值。详见代码。

代码

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

const int N = 1e5 + 10;
int n, sg[N], mex[N];

int main() {
    scanf("%d", &n);
    int ans = -1, cnt = 1;
    for (int i = 3; i <= n; i++) {
        for (int m = 2;; m++) {
            int sum = i - (m - 1) * m / 2; // 都减去变成 a, a, a, a 的形式
            if (sum < 0) break;
            if (sum % m) continue;
            int g = 0;
            for (int j = 0; j < m; j++) g ^= sg[sum / m + j];
            if (!g && i == n && !~ans) ans = m;
            mex[g] = i; // 更新 mex; 如果 mex[g] != i 说明当前 i 的后继没有 sg = g 的。
        }
        for (int j = 0;; j++) {
            if (mex[j] != i) { // sg 函数定义
                sg[i] = j;
                break;
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

例五:AGC002E Candy Piles

题目描述

n n n 堆石子,每堆石子有 a i a_i ai 个,两人轮流操作。要么取走石子最多的一堆,要么将每堆石子取走 1 1 1 个。谁取走最后 1 1 1 个石子,谁就输了。假设两人都足够聪明,求先手必胜还是后手必胜。
( 1 ≤ n ≤ 1 0 5 , 1 ≤ a i ≤ 1 0 9 ) (1 \leq n \leq 10^5,1 \leq a_i \leq 10^9) (1n105,1ai109)

Solution

不妨先按 a i a_i ai 从大到小排序。对于石子最多的一堆,只要不直接取完,那么这堆石子仍然是最多的。
举个例子,对于 { a } = { 7 , 7 , 7 , 6 , 4 , 4 , 4 , 2 , 2 } \{a\} = \{7,7,7,6,4,4,4,2,2\} {a}={7,7,7,6,4,4,4,2,2},排完序后能够得到下图。
uxC5ND.png
对于取走石子最多的一堆,实际就是消去最左边一行;对于取走每堆石子取走 1 1 1 个,实际就是消去最下面一行。
uxCT9H.png
我们不妨再把点阵转化为一个网格图。
从原点开始,每人每次可以选择向右或向上移动一格,向右代表消去最左边一行,向上代表消去最下面一行。很容易发现,当走到网格图的边界(下图中的实线部分)时,所有石子刚好被取完。
uxiGyn.png
显然边界上的点都是必败点,因为谁走到边界谁就输了。
对于任意一个不在边界上的点,如果它的上面和右面都是必败点,那么这个点一定是必胜点。如果它的上面和右面有一个是必胜点,那它就是必败点。(下图用 ○ / × 分别表示必败点和必胜点)
uxite0.png
因为从原点 ( 0 , 0 ) (0,0) (0,0) 出发,所以当原点是必胜点时,后手必胜;原点是必败点时,先手必胜。
将整个网格图构造出来复杂度太大,所以考虑找规律:
除了边界外,同一对角线上的点全部是相同类型的点。
我们可以通过算出原点最右上方且不在边界上的点的类型,来知道原点是必胜点还是必败点。
找到以原点为左下角的最大正方形,设其右上方顶点为 ( i , i ) (i,i) (i,i) 。当它到最上面且不在边界上的点的距离和最右面且不在边界上的点的距离其中一个为奇数时,这个点为必败点,反之这个点为必胜点。
uxiNwV.png

代码

例六:CF388C Fox and Card Game

题目描述

桌子上有 n n n 堆牌。每张牌上都有一个正整数。Ciel 可以从任何非空牌堆的顶部取出一张牌,Jiro 可以从任何非空牌堆的底部取出一张牌。Ciel 先取,当所有的牌堆都变空时游戏结束。他们都想最大化他所拿牌的分数(即每张牌上正整数的和)。问他们所拿牌的分数分别是多少?

题目解析

因为对于每一张牌,如果 A 和 B 都想要,那么归近的一方。
所以每个人都拥有靠近他的一半。
只要分配中间的就行。
显然排序在 先、后、先、后的分配。

代码

#include <bits/stdc++.h>
using namespace std;
int n, k, x, aa, ab;
vector <int> m;
bool cmp (int a, int b) {return a > b;}
int main() {
    cin >> n;
    while (n--) {
        cin >> k;
        for (int i = 1; i <= (k / 2); i++) 
            cin >> x, aa += x;
        if (k & 1) cin >> x, m.push_back(x);
        for (int i = 1; i <= (k / 2); i++) 
            cin >> x, ab += x;
    }
    sort (m.begin(), m.end(), cmp);
    for (int i = 0; i < m.size(); i++) {
        if (i % 2 == 0) aa += m[i];
        else ab += m[i];
    }
    cout << aa << ' ' << ab << '\n';
}

例七:ABC297G Constrained Nim 2

题目描述

给定 n n n 堆喵喵,你(First)和 lbw(Second) 轮流吃,每次可以选其中一堆、然后吃掉 l ∼ r l \sim r lr 个喵喵。谁不能吃喵喵了谁就输了。问谁会赢?

题解

找规律发现 sg(x) = x \texttt{sg(x)}=x % (l + r) / l sg(x)=x
然后用 SG \texttt{SG} SG 定理合并。

示例代码

#include <bits/stdc++.h>
using namespace std;
int n, l, r, SG, x;
int sg(int x) { return x % (l + r) / l;}
int main() {
    cin >> n >> l >> r;
    for (int i = 1; i <= n; i++) {
        cin >> x;
        SG ^= sg(x);
    }
    cout << (SG ? "First" : "Second");
}

参考资料

施工进度

  • 前置知识
  • 前言
  • SG \texttt{SG} SG 函数
  • 巴什博弈 (Bash) \texttt{(Bash)} (Bash)
  • 尼姆博弈 (Nim) \texttt{(Nim)} (Nim)
  • 反尼姆博弈
  • 斐波那契博弈 (Fibonacci) \texttt{(Fibonacci)} (Fibonacci)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zjx-kimi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值