[博弈论] Nim游戏及SG函数(经典+台阶+集合+拆分)

0. 前言

一堆关于基础博弈论算法题的相关定义:

NIM游戏:

给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。

我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。

所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。

NIM博弈不存在平局,只有先手必胜和先手必败两种情况。

定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0

公平组合游戏ICG:

若一个游戏满足:

  1. 由两名玩家交替行动;
  2. 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
  3. 不能行动的玩家判负;

则称该游戏为一个公平组合游戏。

NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件2和条件3。

有向图游戏:

给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。

任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。

Mex运算:

设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即: mex(S) = min{x}, x属于自然数,且x不属于S

SG函数:

在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即: SG(x) = mex({SG(y1), SG(y2), …, SG(yk)}) 特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。

有向图游戏的和:

设G1, G2, …, Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, …,Gm的和。 有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即: SG(G) = SG(G1) ^ SG(G2) ^ …^ SG(Gm)

定理:

有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。

有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。

1. Nim 游戏+模板题

891. Nim游戏

在这里插入图片描述

重点: 式子推导

公式梳理的还是很清楚的,就是字难看…
在这里插入图片描述
总结:初始所有石子异或值为 0,则先手必败,异或值不为 0,则先手必胜。

代码:

#include <iostream>

using namespace std;

int main() {
    int n, res = 0;
    cin >> n;
    while (n --) {
        int a;
        cin >> a;
        res ^= a;
    }
    if (res) puts("Yes");
    else puts("No");
    return 0;
}

2. 台阶 - Nim 游戏+变种题

892. 台阶-Nim游戏

在这里插入图片描述

重点: 式子推导

某些式子需要用到 Nim 游戏的些许证明,翻上去看就行了。字迹潦草…

在这里插入图片描述

总结:初始所有奇数阶石子异或值为 0,则先手必败,异或值不为 0,则先手必胜。

思考:为什么不判断偶数级台阶的异或呢?

  • 理解方式一:必败态是所有奇数阶台阶的异或和为 0,其余的为必胜态。因为每一个这种定义的必败态不论怎么操作,对手都可以再把你转移回必败态;反之任何一个必胜态,一定存在一种转移到必败态的方式。
  • 理解方式二:假如对手处于偶数台阶的异或为 0 的局面,他可以通过把第一个台阶的所有石子全部放到地面上,而轮到我们的时候就只能破坏这个异或为 0 的局面(因为不能移动第 0 个台阶也就是地面,只能移动其他台阶的石子),把必胜的局面(异或为非 0)给了对手。但是作为先手方就是要避免这种情况出现,采取最优策略是不会让对手偶数阶台阶异或为 0,且一号台阶有石子的。那么,当先手方遇到初始偶数阶台阶异或为 0 情况时,拿光 1 号台阶的石子到地面保持自己的必败态?这是不现实的,这就不叫做最优策略,而是最蠢策略了。所以通过判断偶数阶台阶异或为 0 的操作,必胜态与必败态之间的转化是困难的。中间绕的弯很多,暂时没找到很好的策略来解释这个问题。

代码:

#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;
    int res = 0, cnt = 0;
    while (n --) {
        int a;
        cin >> a;
        ++ cnt;
        if (cnt % 2) res ^= a;
    }
    puts(res ? "Yes": "No");
    return 0;
}

3. Mex运算与SG函数

Mex运算:

设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即: mex(S) = min{x}, x属于自然数,且x不属于S

SG函数:

有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1,y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即: SG(x) = mex({SG(y1), SG(y2), …,SG(yk)}) 特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。

  1. SG(k)有k个后继节点,且分别为 0~k-1
  2. 非 0 可以走向 0
  3. 0 只能走向非 0
  4. 多个独立局面的SG值等于这些局面的SG值的异或值

例:若 S = [ 2 , 5 ] S=[2,5] S=[2,5] 表示每次只能取 2 个或 5 个石子, h = 10 h=10 h=10 表示公有 10 个石子,则有展开形式为:
在这里插入图片描述
定理: 对于一个图 G,如果 SG(G) != 0,则先手必胜,反之必败

证明:

  • 如果 SG(G) = !0,则根据性质 2,先手必可以走向 0,所以后手遇见的是 0 的局面,然而 0 局面仅对应终点局面或者只能走向非 0 局面。故先手必胜。

定理: 对于 n 个图,如果 SG(G1) ^ SG(G2) ^ ... ^ SG(Gn) ^ != 0,则先手必胜,反之必败。

证明:

  • 类比 Nim 游戏
  • SG(Gi) = 0 时,xor = 0,显然先手必败,简单理解为所有图中均无石子可取。终止状态必为目前这个局面,但是当前局面并不一定为终止状态
  • xor = x = !0,因为肯定存在一个 SG(Gi) ^ x < SG(xi),而根据 SG() 性质 1,SG(k) 可以走到 0~k-1 的任何一个状态。因此,必定可以从 SG(xi) 走到 SG(xi)^x 状态,那么使得 xor = 0
  • xor = 0 ,类比 Nim 游戏,每移动一个节点,对应的 SG 值必然减小,且 xor != 0。即若 xor = 0,则说明移动的那个节点的值并没有变化,即从 SG(k) 变成了 k,但是这与 SG 函数性质 1 矛盾。这个证明完全与 Nim 游戏证明方式相同。

4. 集合 - Nim 游戏+变种题

893. 集合-Nim游戏

在这里插入图片描述

重点: SG 函数,dfs 求解 SG 值

采用记忆化搜索来求解 SG 值,在此还需理解石子相同那么 SG 值也相同的原因

  • 因为取法方式唯一,那么所能造成的局面是固定的
  • 不论是它作为起点,后序的分支局面都已经确定了
  • 还是它作为某个分支中的一个局面,那么它后序的局面也已经确定了
  • 所以,相同石子的 SG 值相等。所以可以采用记忆化搜索来加速搜索效率

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>

using namespace std;

const int N = 105, M = 1e4+5;

int n, k;
int s[N], f[M];     // s存取石子的方法,f存取所有取法对应的sg值

// dfs求取sg结果
int sg(int x) {
    if (f[x] != -1) return f[x];
    
    unordered_set<int> S;               // 哈希表存所有可以到的局面
    for (int i = 0; i < k; ++i) 
        if (x >= s[i]) 
            S.insert(sg(x - s[i]));     
            
    for (int i = 0 ; ; ++i) 
        if (!S.count(i))
            return f[x] = i;
}

int main() {
    cin >> k;                   // s个固定取法
    
    memset(f, -1, sizeof f);    // 记忆化搜索数组初始化
    
    for (int i = 0; i < k; ++i) cin >> s[i];    
    
    cin >> n;                   // 每堆石子的数量
    
    int res = 0;            
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        res ^= sg(x);
    }
    
    puts(res ? "Yes" : "No");
    return 0;
}

5. 拆分 - Nim 游戏+变种题

894. 拆分-Nim游戏

在这里插入图片描述

重点: SG 函数,dfs 求解 SG 值

思路:

  • 这个相较于上题来讲,本题每一堆可以变成不大于原来那堆的任意大小的两堆。这样保证了最大石子可以一直变小,则一定会结束
  • a 石子分解成 b 石子和 c 石子,肯定需要保证 bc 均小于 a。且为了保证不进行重复计算,枚举的时候 b 大于等于 c 注意下就行了
  • 则此时局面 a,拆分成了两个独立局面 bc,由于 SG 函数性质,多个独立局面的 SG 值等于这些局面 SG 值的异或
  • 所以,哈希表中存的就是 sg(b) ^ sg(c) 这是本题与上题的唯一区别

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>

using namespace std;

const int N = 105;

int n;
int f[N];

int sg(int x) {
    if (f[x] != -1) return f[x];
    
    unordered_set<int> S;
    for (int i = 0; i < x; ++i) 
        for (int j = 0; j <= i; ++j) 
            S.insert(sg(i) ^ sg(j));

    for (int i = 0; ; ++i)
        if (!S.count(i))
            return f[x] = i;
}

int main() {
    cin >> n;
    
    memset(f, -1, sizeof f);
    
    int res = 0; 
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        res ^= sg(x);
    }
    puts(res ? "Yes" : "No");
    return 0;
    
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

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

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

打赏作者

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

抵扣说明:

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

余额充值