Nim博弈和SG函数

详尽版:博弈论 SG函数

各种博弈论
在这里插入图片描述

1.Nim博弈

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

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

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

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

定理: Nim博弈先手必胜,当且仅当 a 1 ⊕ a 2 ⊕ . . . ⊕ a n ≠ 0 a_1\oplus a_2\oplus...\oplus a_n≠0 a1a2...an=0
公平组合游戏ICG

若一个游戏满足:
1. 由两名玩家交替行动;
2. 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;
3. 不能行动的玩家判负;
则称该游戏为一个公平组合游戏。

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


证明:
(1)  0 ⊕ 0 ⊕ . . . ⊕ 0 = 0 0\oplus 0\oplus...\oplus 0=0 00...0=0
(2)  a 1 ⊕ a 2 ⊕ . . . ⊕ a n = x ( ≠ 0 ) a_1\oplus a_2\oplus...\oplus a_n=x(≠0) a1a2...an=x(=0)

 x的二进制表示最高位1在第k位,说明 a 1 − a n a_1-a_n a1an中,必然至少存在一个数 a i a_i ai的第k位二进制数是1,就会有 a i ⊕ x < a i a_i\oplus x<a_i aix<ai
在这里插入图片描述
所以我们可以在 a i a_i ai拿走 a i − ( a i ⊕ x ) a_i-(a_i\oplus x) ai(aix)个石子,此时 a i = ( a i − ( a i − a i ⊕ x ) ) = a i ⊕ x a_i=(a_i-(a_i-a_i\oplus x))=a_i\oplus x ai=(ai(aiaix))=aix

此时就有 a 1 ⊕ a 2 ⊕ . . . ⊕ ( a i ⊕ x ) . . . ⊕ a n = x ⊕ x = 0 a_1\oplus a_2\oplus...\oplus(a_i\oplus x)...\oplus a_n=x\oplus x=0 a1a2...(aix)...an=xx=0

说明从 a i a_i ai中取走 a i − ( a i ⊕ x ) a_i-(a_i\oplus x) ai(aix)个石子之后,就会变成先手必败局面,所以原局面是先手必胜
(3)  a 1 ⊕ a 2 ⊕ . . . ⊕ a n = 0 a_1\oplus a_2\oplus...\oplus a_n=0 a1a2...an=0

不论怎么去拿剩下数的异或值都不会是0

反证法: 假设从 a i a_i ai中拿走一些石子,变成 a i ′ a_i' ai,仍然有 a 1 ⊕ a 2 ⊕ . . . ⊕ a i ′ . . . ⊕ a n = x ⊕ x = 0 a_1\oplus a_2\oplus...\oplus a_i'...\oplus a_n=x\oplus x=0 a1a2...ai...an=xx=0

让原式左部和上述左部异或, ( a 1 ⊕ a 2 ⊕ . . . ⊕ a i . . . ⊕ a n ) ⊕ ( a 1 ⊕ a 2 ⊕ . . . ⊕ a i ′ . . . ⊕ a n ) = a i ⊕ a i ′ = 0 (a_1\oplus a_2\oplus...\oplus a_i...\oplus a_n)\oplus (a_1\oplus a_2\oplus...\oplus a_i'...\oplus a_n)=a_i\oplus a_i'=0 (a1a2...ai...an)(a1a2...ai...an)=aiai=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 1 ⊕ a 2 ⊕ . . . ⊕ a n ≠ 0 a_1\oplus a_2\oplus...\oplus a_n≠0 a1a2...an=0,即先手必胜局面,说明原局面为先手必败

例题1:

在这里插入图片描述

输入
2
2 3
输出
Yes
思路

先手必胜状态:可以走到一个必败状态
先手必败状态:走不到任何一个必败状态

先手必败: 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≠0 a1a2...an=0

#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=105;
const int mod=1e9+7;
int main()
{
    int n;
    cin>>n;
    int xr=0;//0^x=x
    for(int i=0;i<n;i++)
    {
        int x;
        cin>>x;
        xr^=x;
    }
    if(xr)
    cout<<"Yes"<<endl;
    else
    cout<<"No"<<endl;
    return 0;
}
例题2:Nim游戏变形

在这里插入图片描述

输入
3
2 1 3
输出
Yes
思路

需要找到这样一个规律,奇数台阶上石子数目异或和不为0则是先手必胜 a 1 ⊕ a 3 ⊕ . . . ⊕ a n ≠ 0   ( n % 2 = 1 ) a_1\oplus a_3\oplus...\oplus a_n≠0\ (n\%2=1) a1a3...an=0 (n%2=1)

结论证明: a 1 ⊕ a 3 ⊕ . . . ⊕ a n = x a_1\oplus a_3\oplus...\oplus a_n=x a1a3...an=x

1.x≠0
 (1)如果我从奇数阶 a i a_i ai上取走 a i − ( a i ⊕ x ) a_i-(a_i\oplus x) ai(aix)个石子放到下一层后,就会使得 a 1 ⊕ a 3 ⊕ . . . ⊕ a n = 0 a_1\oplus a_3\oplus...\oplus a_n=0 a1a3...an=0,抛给对手时x=0。
 (2)如果对手从偶数阶 a 2 i a_{2i} a2i上取k个石子,我们就可以对应的从她所放石子的那一层取相同数量的放到下一层,就能保持奇数阶石子数不变。
2.x=0
 (1)奇数层上取走任意个后都会使得异或值不再为0。
 (2)对手拿偶数阶不影响奇数阶石子数,也不会影响异或值。

#include <iostream>
#include <cmath>
#include <vector>
#include <unordered_set>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const int mod=1e9+7;
int main()
{
    int n;
    cin>>n;
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        if(i%2)
            ans^=x;
    }
    if(ans)
        cout<<"Yes"<<endl;
    else
        cout<<"No"<<endl;
    return 0;
}

2.SG函数

有向图游戏

 给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。
 任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边

Mex运算

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

     mex(S) = min{x}, x∈N自然数,且x∉S

SG函数

 在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点 y 1 , y 2 , . . . , y k y_1, y_2, ..., y_k y1,y2,...,yk ,定义 SG(x)为x的后继节点 y 1 , y 2 , . . . , y k y_1, y_2, ..., y_k y1,y2,...,yk 的SG函数值构成的集合再执行mex(S)运算的结果,即: 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))

在这里插入图片描述
 特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)

有向图游戏的和

 设 g 1 , g 2 , . . . , g m g_1, g_2, ..., g_m g1,g2,...,gm 是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 的和。
有向图游戏的和的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)
在这里插入图片描述

定理
 (1)有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0
 (2)有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0

例题1:

在这里插入图片描述

输入
2
2 5
3
2 4 7
输出
Yes
代码

在这里插入图片描述

时间复杂度:最多100堆,记忆化搜索每个状态计算一次,一个状态最多k个后继状态,sg计算次数最多为104次,所以总时间复杂度不超过106,O(mk),m为每堆的石子个数

#include <iostream>
#include <cmath>
#include <vector>
#include <unordered_set>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const int mod=1e9+7;
int s[110],sg[maxn];
int k,n;
int SG(int x)//用记忆化搜索来求SG值
{
    if(sg[x]!=-1)//保证每个状态只会被算一次,避免了时间复杂度不为指数级别
        return sg[x];
    //用一个哈希表来存当前局面所能到达的局面
    unordered_set<int> st;
    for(int i=0;i<k;i++)//遍历可以取的石子数
    {
        if(x>=s[i])
        st.insert(SG(x-s[i]));//把这个状态加进去并且求这个状态的SG值
    }
    //找到当前最小的自然数
    for(int i=0;;i++)
        if(st.count(i)==0)//当前状态中没有i,所以最小的自然数就是它了
            return sg[x]=i;
}
int main()
{
    cin>>k;
    for(int i=0;i<k;i++)
        cin>>s[i];
    cin>>n;
    memset(sg,-1,sizeof(sg));//不需要多次初始化sg,因为x值相等对应的SG值是一定的
    int ans=0;//注意初始化
    for(int i=0;i<n;i++)
    {
        int x;
        cin>>x;
        ans^=SG(x);
    }
    if(ans)
        cout<<"Yes"<<endl;
    else
        cout<<"No"<<endl;
    return 0;
}
例题2:

在这里插入图片描述

输入
2
2 3
输出
Yes
思路

 首先游戏是可结束的,因为即使堆数可能会不断增多,但是最大值不断在减小,当有一些堆减少到1时,取走这堆就不能再添加堆了,所以所有堆都是会被取走的。

 这个问题依然是求每一堆的SG值的异或,但是普通版的能够知道当前状态能够转移到的所有状态,只要调用mex()去算即可,但是这里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) 知如果有两堆(即两个独立的图),则
在这里插入图片描述

不同游戏状态转移时的SG计算方式不同,要根据题目去具体分析,本题sg的计算方式是st.insert(SG(i)^SG(j));,上题的计算方式是st.insert(SG(x-s[i]));

#include <iostream>
#include <cmath>
#include <vector>
#include <unordered_set>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const int mod=1e9+7;
int sg[maxn];
int n;
int SG(int x)//用记忆化搜索来求SG值
{
    if(sg[x]!=-1)//保证每个状态只会被算一次,避免了时间复杂度不为指数级别
        return sg[x];
    //用一个哈希表来存当前局面所能到达的局面
    unordered_set<int> st;
    for(int i=0;i<x;i++)//遍历所有可放组合的石子数
        for(int j=0;j<=i;j++)//避免重复,规定i<=j
            st.insert(SG(i)^SG(j));//把这个状态加进去并且求这个状态的SG值

    //mex操作,找到当前最小的自然数
    for(int i=0;;i++)
        if(st.count(i)==0)//当前状态中没有i,所以最小的自然数就是它了
            return sg[x]=i;
}
int main()
{
    cin>>n;
    memset(sg,-1,sizeof(sg));

    int ans=0;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
            ans^=SG(x);
    }
    if(ans)
        cout<<"Yes"<<endl;
    else
        cout<<"No"<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值