博弈论

# 博弈论

# 特点

  • 两个以上玩家
  • 状态为有限的集合
  • 双方轮流操作
  • 具有规则
  • 有限次操作结束
  • 有胜负
  • 两人足够聪明

# 必定点

必胜(败)点:在该点进行操作的人必胜(败)

# 属性

1.1. 所有终结点为必败点
2.2. 每个必胜点至少有一个操作进入必败点
3.3. 必败点只能走入必胜点

# 图示

# 巴什博弈

22 人, nn 枚石子,双方轮流选取,不能拿或且不可拿超过 xx 枚,没石子可拿的人就输了
这个问题由于两个人一定足够聪明,他们会有一种“跟着选”的策略
即:如果当前石子数为 x+1x+1 的倍数,那么如果一个人拿了 aa 个,那么第二个人只要跟着拿 (x+1)−a(x+1)-a 个就一定可以赢

那么如果 nn 是 x+1x+1 的倍数,那么第一个人就一定会输
同时如果不是的话,第一个人可以把这 nn 个石子变成 x+1x+1 的倍数,他就一定可以获胜了

# 尼姆博弈

22 人,多堆石子,每个人选取其中一堆石子取走任意个石子

三堆石子分析
(0,0,0)(0,0,0) 必败
(0,0,x)(0,0,x) 必胜
(0,1,1)(0,1,1) 必败
(0,x,x)(0,x,x) 必败
(x,y,z)(x,y,z) ??

# 尼姆和

按位的异或(上面是 x⊕y⊕zx\oplus y\oplus z)
其中 00 必败,否则必胜

证明: 例如三堆石子为 13,12,813,12,8
13=1101212=11002⊕8=10002nim_sum=10012=9\frac{\begin{aligned}13&=1101_2\\12&=1100_2\\\oplus\;\;\;\;8&=1000_2\end{aligned}}{nim\_sum=1001_2=9}


如果有方法令 99 变成 00 则必胜(属性 ②② )
可以令第一个数变成 01000100 ,则
13=0100212=11002⊕8=10002nim_sum=00002=0\frac{\begin{aligned}13&=0100_2\\12&=1100_2\\\oplus\;\;\;\;8&=1000_2\end{aligned}}{nim\_sum=0000_2=0}
朴素做法:结果从前往后,第一个 11 上面一定是有奇数个 11 ,选择一行该位置为 11 的,这个位置变成 00 ,后面再遇到选取同行并取反
∴\therefore 满足


如果任意方法都会让结果非 00 则必败(属性 ③③ )
本身已经是 00000000 平衡了,即任意位置都有偶数个 11
操作后必定会改变其中一列的 11 的个数成为奇数个,打破平衡使结果非 00 ,必败
∴\therefore 满足

问题

QuestionQuestion AnswerAnswer
判断先手输赢 根据起始状态的尼姆和
求先手拿石子可赢的方案数量 看尼姆和第一位 11 上面有多少个 11 ,因为不可以加石子

# SGSG 函数

xx 节点的 sgsg 值是不等于 xx 后继节点的 sgsg 值的最小非负整数
即: sg(x)=mex({sg[sonx]})sg(x)=mex(\{sg[son_x]\})

其中 00 必败,否则必胜

证明:看巴什博弈(最多可以拿三个)
终结状态 sgsg 必 00 ,非零必胜 (属性 ①① )
能转移到 00 的均必胜(属性 ②② ),在 sgsg 内必定不为 00
不能转移到 00 ,即必须转移到正整数,本身一定是 00 ,必败(属性 ③③ )

# 例

# 1

33 堆石子, 5,7,95,7,9 个。轮流拿石子,必拿但不超过三个。讨论先手的输赢

看作 33 个巴什博弈:
sg(5)=5%4=1sg(5)=5\%4=1
sg(7)=7%4=3sg(7)=7\%4=3
sg(9)=9%4=1sg(9)=9\%4=1
多用尼姆和与 sgsg 函数相结合,全局输赢 =sg(x1)⊕sg(x2)⊕⋯⊕sg(xn)=sg(x_1)\oplus sg(x_2)\oplus\dots\oplus sg(x_n)
本问题中讨论即 res_sgres\_sg 为 00 则先手必败,否则先手必胜

# 2

给定规则数 nn ,即可以拿走 a[1~n]a[1~n] 个石子
给定情况数 mm ,每种情况有 oo 堆牌,每堆 x[1~o]x[1~o] 张,讨论每种情况的先手输赢

程序采用记忆化搜索

int n, m;   // 规则数,情况数
int a[100]; // 规则

int sg[10001];
inline int SG ( int all ) {
        bool vis[101] = {0};
        for ( int i = 0; i < n; i ++ ) {
                int remain = all - a[i]; // 剩余的石子数
                if (remain < 0) break;   // 少于0个不存在
                if ( sg[remain] == -1 ) sg[remain] = SG(remain); // 继续递归子状态的sg值
                vis[sg[remain]] = 1;     // 子状态sg值标记一下
        }
        for ( int i = 0; ; i ++ ) if ( !vis[i] ) return i; // sg定义
}

int main () {
        cin >> n; 
        for ( int i = 0; i < n; i ++ ) cin >> a[i];
        
        sort(a, a + n); // 可以在sg内部选取拿走的石子数量上进行 break 优化
        memset(sg, -1, sizeof sg);
        sg[0] = 0;

        cin >> m; while ( m -- ) {
                int res_sg = 0;

                int o; cin >> o; while ( o -- ) {
                        int x; cin >> x;
                        if ( x == -1 ) sg[x] = SG(x);
                        res_sg ^= sg[x];
                }

                if ( res_sg == 0 ) cout << "先手必败" << endl;
                else cout << "先手必胜" << endl;
        }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Last Updated: 10/14/2023, 7:51:49 PM