公平组合游戏以及SG函数

闲言:这里有关四大博弈的理解以及题目-----------传送门

一、公平组合游戏定义

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

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

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

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

二、必胜态与必败态

1.必胜态:任一后继局面为必败态,当前局面为必胜态
2.必败态:所有后继局面均为必胜态,当前局面为必败态
举个例子,拿石头,我只要有一种拿石子的方式,拿完以后,轮到你,你不管怎么拿我都能赢,那么我就是必胜态,你就是必败态。

三、sg函数及Mex运算

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

2.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)。

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

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

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

一般解题模型:把原组合游戏转化成一个个独立的子游戏,分别求出sg值,最后将他们异或起来,为0先手必败,否则的话先手必胜

+++++++++++++++++++++++++++++++++++++++++++++

以上皆来自《算法竞赛进阶指南》这本书

+++++++++++++++++++++++++++++++++++++++++++++

分割线

自己的一些话:主要还是那例题来说明这个SG函数怎么用,其实也容易明白,把必败点的SG定义为0,是在递归回溯的时候才赋值的,回溯的时候一直往上推,就可以得出SG(n)的值,在这里的话是用到了记忆化搜索技巧,以达到提高效率以及避免重复计算sg(x)的值

在这里插入图片描述

例题一 集合-Nim游戏

给定n堆石子以及一个由k个不同正整数构成的数字集合S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。

输入格式
第一行包含整数k,表示数字集合S中数字的个数。
第二行包含k个整数,其中第i个整数表示数字集合S中的第i个数si。
第三行包含整数n。
第四行包含n个整数,其中第i个整数表示第i堆石子的数量hi。
输出格式
如果先手方必胜,则输出“Yes”。
否则,输出“No”。

数据范围
1≤n,k≤100,
1≤si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出样例:
Yes

# include <iostream>
# include <cstring>
# include <unordered_set>
# include <algorithm>
using namespace std;
const int N = 110,M = 10010;
int n,m;
int s[N],f[M];  //f[]表示sg值
int sg(int x){        //计算头的sg值,因为我只用到头的sg值
    if(f[x]!=-1) return f[x];   //如果发现某个状态计算过,直接返回
    unordered_set<int> S;  //用哈希set来存这个点扩散出去合法状态的sg值
    for(int i=0;i<m;++i){   //看能到达哪个状态
        int sum = s[i];
        if(x>=sum) S.insert(sg(x-sum));
    }
    for(int i=0;;++i){    //直接从0开始遍历,根据mex定义来写,寻找最小的不在这个集合的自然数
        if(!S.count(i)) return f[x]=i;
    }
}
int main(void)
{
    cin>>m;
    for(int i=0;i<m;++i) cin>>s[i];
    cin>>n;
    memset(f,-1,sizeof(f));    //初始化,在dfs中发现不是-1就表明该状态计算过,直接返回
    int res = 0;
    for(int i=0;i<n;++i){
        int x;
        cin>>x;
        res^=sg(x);
    }
    if(res) puts("Yes");
    else puts("No");
    return 0;
}

给张丑图理解理解

例题二 拆分-Nim游戏

给定n堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。

输入格式
第一行包含整数n。
第二行包含n个整数,其中第i个整数表示第i堆石子的数量ai。
输出格式
如果先手方必胜,则输出“Yes”。
否则,输出“No”。

数据范围
1≤n,ai≤100

思路:跟上一道题整体代码没什么两样,唯一区别的就是把一堆分成两堆的SG值怎么求?这里就是直接用到SG的一些概念,前面说到:

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

就是SG(x)=SG(x1)^SG(x2),因此也就这一步不一样,其他的代码是一样的。值得注意的是,1 1 是先手赢,因为先手可以把一堆石头(里面只有一个)分成两堆石头(里面都为0)。也就是说可以分成0个石头一堆

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

using namespace std;
const int N = 110;
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 ++ ){
            cout<<i<<' '<<j<<endl;
            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;
    while (n -- )
    {
        int x;
        cin >> x;
        res ^= sg(x);
    }
    if (res) puts("Yes");
    else puts("No");
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值