【刷题】数学知识——博弈论:NIM游戏

博弈论分类

博弈论的游戏主要分为:

  1. 公平组合游戏:
    游戏有两个人参与,二者轮流做出决策,双方均知道游戏的完整信息;
    任意一个游戏者在某一确定状态可以作出的决策集合,只与当前的状态有关,而与游戏者无关;
    游戏中的同一个状态不可能多次抵达,游戏以玩家无法行动为结束,且游戏一定会在有限步后以非平局结束。

  2. 非公平组合游戏
    在非公平组合游戏中,游戏者在某一确定状态可以做出的决策集合与游戏者有关。大部分的棋类游戏都不是公平组合游戏,如国际象棋、中国象棋、围棋、五子棋等(因为双方都不能使用对方的棋子)。

  3. 反常游戏
    胜者为第一个无法行动的玩家。以 Nim 游戏为例,Nim 游戏中取走最后一颗石子的为胜者,而反常 Nim 游戏中取走最后一刻石子的为败者。


NIM游戏

游戏定义:给定 n n n堆物品,第 i i i堆物品有 A i A_i Ai个。两名玩家轮流行动,每次可以人选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一样物品的人获胜。两人都采取最优策略,稳先手能否必胜。

可以看到NIM游戏是一个公平组合游戏

NIM游戏不存在平局,只有先手必胜先手必败
先手必胜:只要存在某种行动,使得后手一定会输掉游戏
先手必败:无论采取何种行动,先手都会输掉游戏

定理

先手必胜:当且仅当 A 1 ⊕ A 2 ⊕ . . . ⊕ A n ≠ 0 A_1 \oplus A_2 \oplus ... \oplus A_n \neq 0 A1A2...An=0
先手必败:当且仅当 A 1 ⊕ A 2 ⊕ . . . ⊕ A n = 0 A_1 \oplus A_2 \oplus ... \oplus A_n = 0 A1A2...An=0
⊕ \oplus 表示异或(XOR)

证明

结论1

当场上不存在石子时,此时 A 1 ⊕ A 2 ⊕ . . . ⊕ A n = 0 ⊕ 0 ⊕ . . . ⊕ 0 = 0 A_1 \oplus A_2 \oplus ... \oplus A_n = 0 \oplus 0 \oplus... \oplus 0=0 A1A2...An=00...0=0

结论2

当场上石子满足 A 1 ⊕ A 2 ⊕ . . . ⊕ A n ≠ 0 A_1 \oplus A_2 \oplus ... \oplus A_n \neq 0 A1A2...An=0时,一定可以在某一堆 A i A_i Ai中取若干石子,使得场上石子变成 A 1 ⊕ A 2 ⊕ . . . ⊕ A n = 0 A_1 \oplus A_2 \oplus ... \oplus A_n = 0 A1A2...An=0

  • x = A 1 ⊕ A 2 ⊕ . . . ⊕ A n x=A_1 \oplus A_2 \oplus ... \oplus A_n x=A1A2...An,则 x x x最高位的1,一定由奇数个A的最高位组成,不妨设其中一个是 A i A_i Ai
  • x ⊕ A i x \oplus A_i xAi将会令 x x x的最高位变成0,而 A i A_i Ai最高位仍是1,故 x ⊕ A i < A i x \oplus A_i<A_i xAi<Ai
  • A i A_i Ai这堆石子中拿走 A i − ( x ⊕ A i ) A_i-(x \oplus A_i) Ai(xAi)个石子,剩下 x ⊕ A i x \oplus A_i xAi个石子。
  • 场上所有石子异或
    A 1 ⊕ . . . ( x ⊕ A i ) . . . ⊕ A n A_1 \oplus ... (x \oplus A_i) ... \oplus A_n A1...(xAi)...An
    = A 1 ⊕ . . . ( ( A 1 ⊕ . . . ⊕ A n ) ⊕ A i ) ⊕ . . . ⊕ A n = A_1 \oplus ... ((A_1 \oplus ... \oplus A_n) \oplus A_i) \oplus... \oplus A_n =A1...((A1...An)Ai)...An
    = ( A 1 ⊕ A 1 ) ⊕ ( A 2 ⊕ A 2 ) ⊕ . . . ⊕ ( A n ⊕ A n ) = (A_1 \oplus A_1) \oplus (A_2 \oplus A_2) \oplus...\oplus (A_n \oplus A_n) =(A1A1)(A2A2)...(AnAn)
    = 0 =0 =0

结论3

当场上石子满足 A 1 ⊕ A 2 ⊕ . . . ⊕ A n = 0 A_1 \oplus A_2 \oplus ... \oplus A_n = 0 A1A2...An=0时,无论怎么取石子,都会导致场上石子变成 A 1 ⊕ A 2 ⊕ . . . ⊕ A n ≠ 0 A_1 \oplus A_2 \oplus ... \oplus A_n \neq 0 A1A2...An=0

反证:若取后场上石子变成 A 1 ⊕ A 2 ⊕ . . . ⊕ A n = 0 A_1 \oplus A_2 \oplus ... \oplus A_n = 0 A1A2...An=0

  • 不妨设在 A i A_i Ai堆取了石子,则取后石子个数为 A i ′ A_i' Ai
  • 将取前和取后的石子状态互相异或
    ( A 1 ⊕ A 2 ⊕ . . . ⊕ A i ⊕ . . . ⊕ A n ) ⊕ ( A 1 ⊕ A 2 ⊕ . . . ⊕ A i ′ ⊕ . . . ⊕ A n ) = 0 (A_1 \oplus A_2 \oplus ... \oplus A_i \oplus... \oplus A_n) \oplus ( A_1 \oplus A_2 \oplus ... \oplus A_i' \oplus... \oplus A_n) = 0 (A1A2...Ai...An)(A1A2...Ai...An)=0
  • 异或满足交换律,又因为 A 1 ⊕ A 1 = 0 , A 2 ⊕ A 2 = 0 A_1 \oplus A_1=0, A_2 \oplus A_2=0 A1A1=0,A2A2=0
    上式化简为 A i ⊕ A i ′ = 0 A_i \oplus A_i'=0 AiAi=0,得到 A i = A i ′ A_i=A_i' Ai=Ai
  • 又因为每个人不能不取石子, A i ≠ A i ′ A_i \neq A_i' Ai=Ai,矛盾

综上

当上面三个结论成立时,可以推断出 A 1 ⊕ A 2 ⊕ . . . ⊕ A n ≠ 0 A_1 \oplus A_2 \oplus ... \oplus A_n \neq 0 A1A2...An=0使得先手必胜。
这是因为如果初始的石头满足 A 1 ⊕ A 2 ⊕ . . . ⊕ A n ≠ 0 A_1 \oplus A_2 \oplus ... \oplus A_n \neq 0 A1A2...An=0,先手可以使石头变成 A 1 ⊕ A 2 ⊕ . . . ⊕ A n = 0 A_1 \oplus A_2 \oplus ... \oplus A_n = 0 A1A2...An=0,而后手无论怎么取都会使石头变成 A 1 ⊕ A 2 ⊕ . . . ⊕ A n ≠ 0 A_1 \oplus A_2 \oplus ... \oplus A_n \neq 0 A1A2...An=0,注意到石头取完的时候, A 1 ⊕ A 2 ⊕ . . . ⊕ A n = 0 A_1 \oplus A_2 \oplus ... \oplus A_n = 0 A1A2...An=0,那么石头取完的情况一定会出现在后手取石头的时候。


代码

在这里插入图片描述

#include <iostream>
using namespace std;

int n, a, b;

int main() {
    scanf("%d", &n);
    while(n -- ) {
        scanf("%d", &a);
        b ^= a;
    }
    if (!b) printf("No\n");
    else printf("Yes\n");
    return 0;
}

集合NIM游戏

和NIM游戏不同的是,每次取得石子数只能是规定的某几个数之一。
在这里插入图片描述

概念

要想解决这个问题,需要了解SG函数及其相关概念

Mex运算

求出最小的不属于集合S的非负整数
m e x ( S ) = m i n x ∈ N , x ∉ S { x } mex(S)=min_{x\in \mathbb{N}, x\notin S}\{ x\} mex(S)=minxN,x/S{x}
例如 S = { 0 , 2 , 3 } S=\{ 0,2,3 \} S={0,2,3},则 m e x ( S ) = 1 mex(S)=1 mex(S)=1

有向图游戏

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

任何一个公平组合游戏都可以转化有向图游戏:每个局面相当于图的一个节点,合法行动是图的边。

SG函数

设有向图游戏中,节点 x x x k k k条有向边,分别到达节点 y 1 , y 2 , . . . , y k y_1,y_2,...,y_k y1,y2,...,yk,定义 S G ( x ) SG(x) SG(x) x x x的后继节点 y 1 , y 2 , . . . , y k y_1,y_2,...,y_k y1,y2,...,yk S G SG SG函数值构成的集合再执行 m e x mex mex运算的结果。
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函数值。

上述定义,说白了就是找 x x x最小不能到达的数字。

在这里插入图片描述以上图为例

  • f f f c c c谁都不能到达,最小不能到的数为0。
  • e e e能到 f f f的0,最小不能到的数是1。
  • b b b能到 e e e的1和 c c c的0,最小不能到的数是2。
  • d d d只能到 e e e的1,不能到0
  • a a a只能到 d d d的0,不能到1

有向图游戏的和

设当前有 G 1 , G 2 , . . . , G m G_1,G_2,...,G_m G1,G2,...,Gm总共 m m m个有向图游戏,定义有向图游戏 G G G,它的行动规则是任选某个有向图 G i G_i Gi,并在 G i G_i Gi上行动一步。 G G G就是有向图游戏 G 1 , G 2 , . . . , G m G_1,G_2,...,G_m G1,G2,...,Gm的和。
G G G S G SG SG函数值等于包含的各个子游戏的 S G SG SG函数值异或和。
S G ( G ) = S G ( G 1 ) ⊕ S G ( G 2 ) ⊕ . . . ⊕ S G ( G m ) SG(G)=SG(G_1) \oplus SG(G_2) \oplus ... \oplus SG(G_m) SG(G)=SG(G1)SG(G2)...SG(Gm)

像NIM游戏的每堆石子就是一个有向图游戏 G i G_i Gi,整个NIM游戏看作有向图游戏的和。

定理

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

可以看到:

  • 在没有出边的节点, S G = 0 SG=0 SG=0,对应着败局。
  • S G ≠ 0 SG\neq 0 SG=0的节点一定能走到 S G = 0 SG=0 SG=0的节点。
  • S G = 0 SG=0 SG=0的节点无论怎么走,都会走到 S G ≠ 0 SG\neq 0 SG=0的节点。

先手只要在 S G ≠ 0 SG\neq 0 SG=0的节点,一定可以让后手处于 S G = 0 SG=0 SG=0的节点;后手处于 S G = 0 SG=0 SG=0的节点,无论怎么走都会走到 S G = 0 SG=0 SG=0的节点;保持这样的关系,最后后手一定会走到没有出边的败局上。
可见 S G = 0 SG=0 SG=0的节点就是败局, S G ≠ 0 SG\neq 0 SG=0的节点就是胜局。

有向图游戏的和证明类似NIM游戏的三个结论。

题目及思路

这题的每堆石子,都是一个有向图游戏。所有石子组成一个有向图游戏的和。
因此只需判断 S G ( G ) = S G ( G 1 ) ⊕ S G ( G 2 ) ⊕ . . . ⊕ S G ( G m ) ≠ 0 SG(G)=SG(G_1) \oplus SG(G_2) \oplus ... \oplus SG(G_m) \neq 0 SG(G)=SG(G1)SG(G2)...SG(Gm)=0就是胜局。

问题的关键就是如何求一堆石子的SG函数的值,也就是有向图起点SG函数的值。

一堆10个石子,集合 S = { 2 , 5 } S=\{2,5\} S={2,5}的有向图如下:
在这里插入图片描述节点内的数字代表石子剩余个数,例如10个石子有两种取法,取5个到节点5,取2个到节点8。

代码

#include <iostream>
#include <cstring>
using namespace std;

const int N = 105;

int k, n, s[N], h, ans;
int f[10005];

int dfs(int x) {
    if (f[x] != - 1) return f[x];
    bool flag[105];
    memset(flag, 0, sizeof(flag));
    for (int i = 0; i < k; i ++ ) {
        if (x >= s[i]) {
            flag[dfs(x - s[i])] = 1;
        }
    }
    for (int i = 0; i < N; i ++ ) {
        if (!flag[i]) return f[x] = i;
    }
}

int main() {
    scanf("%d", &k);
    for (int i = 0; i < k; i ++ ) scanf("%d", &s[i]);
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) {
        scanf("%d", &h);
        memset(f, -1, sizeof(f));
        int sg = dfs(h);
        ans ^= sg;
    }
    if (ans) printf("Yes\n");
    else printf("No\n");
    return 0;
}

也可以用unordered_set代替flag数组

int dfs(int x) {
    if (f[x] != - 1) return f[x];
    unordered_set<int> num;
    for (int i = 0; i < k; i ++ ) {
        if (x >= s[i]) {
            num.insert(dfs(x - s[i]));
        }
    }
    for (int i = 0; i < N; i ++ ) {
        if (!num.count(i)) return f[x] = i;
    }
}

台阶NIM游戏

在这里插入图片描述
  看到这题,首先想到的思路就是对前缀和进行异或,例如三级台阶从上到下各有1 1 1的石子,则对1 2 3进行异或,判断其是否为0。这看上去似乎更符合上级台阶的石子能拿到下级台阶的特性。
  但仔细回顾NIM游戏的证明,必须要有两个状态稳定转换,先手一定能将状态1转化到状态2,后手只能状态2转化成状态1
但很显然前缀和异或是否为0这种状态定义的方式,并不符合上述稳定转化。例如三级台阶的石子分别为1 0 2,对1 1 3进行异或为3,3不为0,是状态1;但先手无论是操作成1 0 1、1 0 0、0 1 2,前缀和分别为1 1 2、1 1 1、0 1 3、异或结果都不为0,仍然是状态1,无法转化成状态2。

要想保证状态稳定,先手必须要将后手的操作抵消

  •   在NIM游戏中,如果只有两堆石子,先手是将石子拿到和另一堆一样来使得异或为0,之后后手拿多少先手也拿多少,以此抵消后手对状态的变化。
  •   在台阶NIM游戏中,对于后手从台阶上拿下一级石子,先手也能仿照后手拿下一级,但这时如果还是对每级台阶都进行异或,状态仍然会改变。
      如果只考虑奇数偶数级台阶的异或,先手的操作就能抵消后手的影响。
      具体来说:如果后手拿的是不考虑的台阶上的石子,先手直接将后手拿的石子继续往下一级台阶拿,要考虑的台阶石子不变,异或结果不变;如果后手拿的是要考虑的台阶上的石子,类似NIM游戏的结论2,先手仍能把要考虑的台阶上的石子拿成异或为0(例如只有两个要考虑的台阶,先手可以把另一个要考虑的台阶上的石子拿到和后手拿完一样)。
      最后的问题就是要考虑奇数还是偶数的台阶?考虑总共有两级台阶的情况,当第1级台阶有任意个石子的时候,先手是必胜的,而当第1级台阶没有石子的时候,先手必败,1是奇数,因此考虑奇数的台阶,异或不等于0就是先手必胜。

综上,只考虑奇数台阶的石子异或是否为0,不为0则先手必胜。

#include <iostream>
using namespace std;

int n, a, ans;

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) {
        scanf("%d", &a);
        if (i & 1) ans ^= a;
    }
    if (ans) printf("Yes\n");
    else printf("No\n");
    return 0;
}

拆分NIM游戏

在这里插入图片描述这边题目有一些歧义,放入并不是指把石子放入场上有的石子中,而是直接摆上新的两堆石子。例如拿走一堆10个的石子,加入新的两堆9个的石子。

由于每次拆分都会令最大值减少,所以这个游戏最后能够停止。

注意这题和集合NIM游戏不同,集合NIM游戏是在同一个有向图游戏上进行的,这边拆分后会变成两个有向图游戏,所以要求这两个有向图游戏的和的SG值,需要将这两个有向图游戏的SG值异或

#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;

int n, a, ans, f[105];

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

int main() {
    scanf("%d", &n);
    memset(f, -1, sizeof(f));
    for (int i = 0; i < n; i ++ ) {
        scanf("%d", &a);
        ans ^= dfs(a);
    }
    if (ans) printf("Yes\n");
    else printf("No\n");
    return 0;
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值