ch3(数学)——博弈论(Nim游戏、SG函数)

1.自然语言描述
公平组合游戏(ICG)是指满足:1.游戏有两个参与者,轮流决策,每个人都知道游戏的完整信息;2.游戏者在某一状态可以做出的决策集合仅取决于当前状态,与游戏者本人无关;3.游戏进行中,任何一个状态都不能多次到达,游戏一定会决出胜负。

Nim 游戏:有n堆石子,第i堆石子有si个,两个玩家轮流取走任意一堆的任意多个石子(但不能不取)谁最后没的取判输(取走最后一个物品的人获胜)
Nim 和:把每一堆的石子数进行异或运算,s1 ^ s2 ^ s3 ^ … ^ sn = x;
定义必胜状态为先手必胜状态,必败状态为先手必败状态;必胜状态一定有:s1 ^ s2 ^ … ^ sn = x !=0,即异或结果不为0;必败状态反之,异或结果一定为0。对于 Nim 游戏有三个定理:

①没有后继状态的状态为必败状态,即全0状态,s1 ^ s2 ^ … ^ sn = 0。
②对于s1 ^ s2 ^ … ^ sn != 0的状态,必然存在至少一个后继状态为s1 ^ s2 ^ … ^ sn = 0(必胜状态的后继状态至少有一个必败状态)
③对于s1 ^ s2 ^ … ^ sn = 0的状态,其所有后继状态一定是必胜状态。
证明:oi wiki 博弈论内容

有向图游戏:大部分公平组合游戏都能够转换为有向图游戏,因为游戏的所有状态能够以若干个有向图来表示。有向图游戏本身是,一张有向图,起点上只有一个棋子,两名玩家轮流按边的方向移动棋子,最后不能继续移动棋子的一方判输。

mex 运算:对于一个状态集合S(由若干个自然数组成)mex(S)=min{N(自然数集)-S},即自然数集N与S的差集中最小的自然数。

SG 函数:对于一个状态x,SG(x)=mex{SG(y1),SG(y2),…,SG(yk)}(y1,y2,…,yk 为x的所有后继状态)根据Nim游戏的3个定理,也可以类似地得出SG函数的三个定理:

①SG(0)=0。
②当n个有向图的起点的SG函数异或结果为非0时先手必胜;即SG(s1) ^ SG(s2) ^ SG(s3) ^ … ^ SG(sn) != 0为先手必胜。
③SG(s1) ^ SG(s2) ^ … ^ SG(sn) = 0为先手必败。

2.代码描述
题目:Acwing.891 Nim游戏题目链接

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int main(void)
{
    int n;
    cin>>n;
    
    //先手必胜状态:a1^a2^a3^...^ai^...^an!=0
    //先手必败状态:a1^a2^a3^...^ai^...^an=0
    
    int res=0;
    while(n--){
        int x;
        cin>>x;
        res^=x;
    }
    
    if(res)
        cout<<"Yes"<<endl;
    else
        cout<<"No"<<endl;
}

题目:Acwing.892 台阶-Nim游戏题目链接

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

int main(void)
{
    int n;
    cin>>n;
    
    //先手必胜状态:a1^a3^...^ai^...^an!=0,(i,n均为奇数)
    //先手必败状态:a1^a3^...^ai^...^an=0
    int res=0;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        if(i%2)
            res^=x;
    }
    
    if(res)
        cout<<"Yes"<<endl;
    else
        cout<<"No"<<endl;
    
    return 0;
}

题目:Acwing.893 集合-Nim游戏题目链接

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

using namespace std;

const int MAXN=110,MAXM=10010;

int s[MAXN],f[MAXM];//s存储拿石子的方式,f[i]表示当前局面为i时的sg函数值
int n,k;

//记忆化递归,效率较高
int sg(int x)
{
    if(f[x]!=-1)
        return f[x];
        
    unordered_set<int> S;//哈希表S存储当前局面x能够到达的所有后继局面
    for(int i=0;i<k;i++)
        if(x>=s[i])//可以按s[i]拿走石子
            S.insert(sg(x-s[i]));//插入石子剩x-s[i]的后继局面
    
    for(int i=0;;i++)//计算当前局面的mex
        if(!S.count(i))
            return f[x]=i;
}

int main(void)
{
    cin>>k;
    for(int i=0;i<k;i++)
        cin>>s[i];
        
    cin>>n;
    
    //sg(x)=mex{sg(y1),sg(y2),...,sg(yk)};y1,y2,...,yk均为x的后继局面
    //mex{S}=min(自然数集N-自然数子集S)
    
    memset(f,-1,sizeof(f));
    
    int res=0;
    while(n--){
        int x;
        cin>>x;
        res^=sg(x);
    }
    
    if(res)
        cout<<"Yes"<<endl;
    else
        cout<<"No"<<endl;
        
    return 0;
}

题目:Acwing.894 拆分-Nim游戏题目链接

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

using namespace std;

const int MAXN=110;

int f[MAXN];

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));//两个拆分出来的相互独立的后继状态sg异或和是当前状态的sg值
            //仔细体会,这里体现了sg函数的本质意义
    
    for(int i=0;;i++)
        if(!S.count(i))
            return f[x]=i;
}

int main(void)
{
    int n;
    cin>>n;
    
    memset(f,-1,sizeof(f));
    
    int res=0;
    while(n--){
        int x;
        cin>>x;
        res^=sg(x);
    }
    
    if(res)
        cout<<"Yes"<<endl;
    else
        cout<<"No"<<endl;
    
    return 0;
}

3.一些感想
博弈论对于我这种笨笨来说十分烧脑,公平组合游戏中的知识对于我来说需要花费很长的时间才能正确理解知识中每个定理的意义。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值