【博弈论】从Nim游戏到SG函数

在博弈论中,有这样一类问题:公平组合游戏(Impartial Combinatorial Games,ICG)

若一个游戏满足:

  1. 由两名玩家交替行动;
  2. 游戏中任意时刻,合法操作集合只取决于这个局面本身;
  3. 若轮到某位选手时,若该选手无合法操作,则这名选手判负;

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

先来说两个概念:必胜态必败态

  1. 必胜态:若双方都采取最佳策略,那么持有当前状态的人必胜,当前状态则为必胜态。
  2. 必败态:若双方都采取最佳策略,那么持有当前状态的人必败,当前状态则为必胜态;

如果将每个状态表示为一个节点,那么能走到必败态的一定是必胜态走不到任何必败态的一定是必败态

Nim游戏

ICG中一种叫做Nim游戏,它是最简单的且最常见的博弈论问题,学习博弈论的话首先要掌握这个问题。
Nim游戏的规则是:有 n n n堆石子,第 i i i堆石子有 a i a_i ai个,每次可以从某一堆中取走若干个,先后手轮流取,最后无石子可取的人判负。
对于这个游戏,如果双方都采用最佳策略的话,那么从开局就已经决定胜负了。现在要讨论的是,在每一步都是最佳策略的情况下,先手的人是必败还是必胜

先给出结论 a 1 ⊕ a 2 ⊕ … ⊕ a n = 0 a_1⊕a_2⊕…⊕a_n=0 a1a2an=0,那么先手必败;否则先手必胜

下面给出一个简单的证明

  1. 我们已知, a 1 = a 2 = … = a n = 0 a_1=a_2=…=a_n=0 a1=a2==an=0为必败态,此时 0 ⊕ 0 ⊕ … ⊕ 0 = 0 0⊕0⊕…⊕0=0 000=0;
  2. a 1 ⊕ a 2 ⊕ … ⊕ a n = x ≠ 0 a_1⊕a_2⊕…⊕a_n=x ≠0 a1a2an=x=0证明:一定可以通过某种方式让 a 1 ⊕ a 2 ⊕ … ⊕ a n = 0 a_1⊕a_2⊕…⊕a_n=0 a1a2an=0
    x x x的二进制表示中最高的一位1是第 k k k位;
    那么说明 a 1 , a 2 … a n a_1,a_2…a_n a1,a2an中至少存在一个数第 k k k位是1;
    不难得出 a i ⊕ x < a i a_i⊕x<a_i aix<ai,那么此时从第 i i i堆石子中取走 a i − ( a i ⊕ x ) a_i-(a_i⊕x) ai(aix)个,还剩下 a i ⊕ x a_i⊕x aix个,即 a i ′ = a i ⊕ x a_i'=a_i⊕x ai=aix
    此时 a 1 ⊕ a 2 ⊕ … ⊕ a i ′ ⊕ a i + 1 ⊕ … ⊕ a n = a 1 ⊕ a 2 ⊕ … ⊕ a i ⊕ x ⊕ a i + 1 ⊕ … ⊕ a n = x ⊕ x = 0 a_1⊕a_2⊕…⊕a_i'⊕a_{i+1}⊕…⊕a_n=a_1⊕a_2⊕…⊕a_i⊕x⊕a_{i+1}⊕…⊕a_n=x⊕x=0 a1a2aiai+1an=a1a2aixai+1an=xx=0
    得证;
  3. a 1 ⊕ a 2 ⊕ … ⊕ a n = 0 a_1⊕a_2⊕…⊕a_n=0 a1a2an=0(1),证明:不管怎样拿,剩下的异或起来一定不为0
    不妨设在第 i i i堆拿,此时还剩下 a i ′ a_i' ai个;
    假设 a 1 ⊕ a 2 ⊕ … ⊕ a i ′ ⊕ a i + 1 ⊕ … ⊕ a n = 0 a_1⊕a_2⊕…⊕a_i'⊕a_{i+1}⊕…⊕a_n=0 a1a2aiai+1an=0(2),那么(2)⊕(1)得:
    a i ⊕ a i ′ = 0 a_i⊕a_i'=0 aiai=0,但 a i ≠ a i ′ a_i≠a_i' ai=ai,矛盾,因此原结论成立。
  4. 如果先手的人在开局时面对一个非零状态,那么它一定可以让状态变为零,此时后手的任何操作都会让状态变为非零……以此类推,先手的人永远可以出于非零态,而后手的人一定永远处于零态,再加上游戏进行到最后一定会结束,所以 0 ⊕ 0 ⊕ … ⊕ 0 = 0 0⊕0⊕…⊕0=0 000=0的状态一定会被后手的人遇到,那么先手的人一定会赢;否则先手的人一定会输。

SG函数

先给出一些前置知识:

  1. Mex运算:设S表示一个非负整数集合。 定义 mex(S) 为求出不属于集合 S 的最小非负整数的运算,即: mex(S) = min{x}, x 属于自然数,且 x 不属于 S 也就是集合里面不存在的最小的自然数,比如 mex{1, 2, 3} = 0;
  2. SG函数:对于一个有向无环图(Directed Acyclic Graph,DAG),对于其终点(即没有任何出度的点)的SG值定义为0,对于某点 x x x,设它指向的节点为 y 1 , y 2 , … , y k y_1,y_2,…,y_k y1,y2,,yk,那么 S G ( x ) = M e x { S G ( y 1 ) , S G ( y 2 ) , … , S G ( y k ) } SG(x)=Mex\{SG(y_1),SG(y_2),…,SG(y_k)\} SG(x)=Mex{SG(y1),SG(y2),,SG(yk)}

现在把ICG游戏的么每个状态抽象为图中的一个节点,每次操作后都可以从当前节点转移到下一个节点。每个状态都有一个对应的SG值,而终点(也即无法继续进行游戏的状态)的SG值设置为0,然后就可以根据公式 S G ( x ) = M e x { S G ( y 1 ) , S G ( y 2 ) , … , S G ( y k ) } SG(x)=Mex\{SG(y_1),SG(y_2),…,SG(y_k)\} SG(x)=Mex{SG(y1),SG(y2),,SG(yk)}来计算每一个点的SG值了。这里可以递归处理节点,把每个节点指向的点的SG值存到一个集合set中,再用mex运算求解当前节点的SG值即可。这样就可以得到初始状态的SG值了。

这里给出一道例题来具体演示一下:
原题acwing 集合-Nim游戏

给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 k,表示数字集合 S 中数字的个数。
第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i 个数 s i s_i si
第三行包含整数 n。
第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 h i h_i hi
输出格式
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围
1≤n,k≤100,
1≤ s i , h i s_i,h_i si,hi≤10000
输入样例
2
2 5
3
2 4 7
输出样例
Yes

先给出结论:把所有石头堆初始的SG值异或起来,若不为0则先手必胜,否则先手必败

举个例子:假设有一堆石子,有10个,每次可以拿3个或5个,那么做出游戏状态转化图:
请添加图片描述
对于单个游戏图,可以看到,任何一个SG非零的节点都能转移到一个SG为零的节点。因此循环往复下去,在进行最佳决策的情况下,处于SG为零状态的人一定会输

对于多个图的证明,同Nim游戏的证明思路完全一样,这里就交给大家思考了。

注意这里的递归要用记忆化搜索来进行。因为对于两堆有相同的石子数量,其SG值是一样的。用记忆化搜索可以大大提高效率。下面给出代码:

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

using namespace std;

const int N = 110, M = 10010;

int n, m;
int s[N], f[M];

int sg(int x)
{
    if (f[x] != -1) return f[x];
    
    unordered_set<int> S;
    for (int i = 0; i < n; i ++ )
    {
        int sum = s[i];
        if (x >= sum) S.insert(sg(x - sum));
    }
    
    for (int i = 0; ; i ++ )
        if (!S.count(i))
            return f[x] = i;
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> s[i];
    cin >> m;
    
    memset(f, -1, sizeof f);
    
    int res = 0;
    for (int i = 0; i < m; i ++ )
    {
        int x;
        cin >> x;
        res ^= sg(x);
    }
    
    if (res) puts("Yes");
    else puts("No");
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值