博弈论——Nim游戏

Nim游戏

通常的Nim游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗,不能不拿,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。

在这里定义两个状态
必胜状态:如果这个状态可以转移到必败状态,那么这么状态就是必胜状态
必败状态:如果这个状态转移到的所有状态都是必胜状态,那么这个状态就是必败状态

对于这个游戏给出结论
当a1^ a2 ^… ^ an=0,则先手必败,否则先手必胜

证明:
当a1^ a2 ^… ^ an!=0时,一定存在合法移动使得a1 ^ a2 ^… ^ an=0
当a1^ a2 ^… ^ an=0时,一定不存在合法移动使得a1 ^ a2 ^… ^ an=0

Nim游戏

#include <bits/stdc++.h>
using namespace std;
int main()
{
    
    
    int n;
    cin>>n;
    int res=0;
    while(n--)
    {
    
    
        int x;
        cin>>x;
        res=res^x;
    }
    if(res) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    return 0;
}

台阶-Nim游戏

因为最后一次移动的石子必然时从第一级移动到第零级
因此偶数级台阶可以不用进行考虑
所以可以将奇数级看作是一个经典nim游戏

#include <bits/stdc++.h>
using namespace std;;
int main()
{
    
    
    int n;
    cin>>n;
    int res=0;
    while(n--)
    {
    
    
        int a;
        cin>>a;
        if(n%2!=0) res=res^a;
    }
    if(res) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    return 0;
}

集合-Nim游戏

SG函数:任何一个公平组合游戏都可以通过把每个局面看成一个顶点,对每个局面和它的子局面连一条有向边来抽象成这个“有向图游戏”。下 面我们就在有向无环图的顶点上定义Sprague-Grundy函数。首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{1,3,5}=0、mex{}=0。
在这里插入图片描述
在这里可以将所有的根节点的sg值看作是一个经典nim游戏

在这里利用记忆化搜索来求每一个节点的sg值

#include <bits/stdc++.h>
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> vis;
    for(int i=0;i<m;i++)
    {
    
    
        int sum=s[i];
        if(x>=sum) vis.insert(sg(x-sum));
    }
    for(int i=0;;i++)
    {
    
    
        if(!vis.count(i)) return f[x]=i;
    }
}
int main()
{
    
    
    cin>>m;
    for(int i=0;i<m;i++) cin>>s[i];
    memset(f,-1,sizeof(f));
    cin>>n;
    int res=0;
    for(int i=0;i<n;i++)
    {
    
    
        int x;
        cin>>x;
        res^=sg(x);
    }
    if(res) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    return 0;
}

拆分-Nim游戏

这里依然用到sg函数
在sg函数中有一个重要的性质:sg(b1,b2)=sg(b1)^sg(b2)
在这道题中,每一个状态可能会分成两堆,那么这个状态的sg值可以通过上面的公式求出

将每个初始态的sg值求出,这个问题就可以转化为经典的nim游戏

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int f[N];
int sg(int x)
{
    
    
    if (f[x] != -1)
        return f[x];
    unordered_set<int> vis;
    for (int i = 0; i < x; i++)
        for (int j = 0; j <= i; j++) //避免重复枚举
            vis.insert(sg(i) ^ sg(j));
    for (int i = 0;; i++)
    {
    
    
        if (!vis.count(i))
            return f[x] = i;
    }
}
int main()
{
    
    
    int n;
    cin >> n;
    memset(f, -1, sizeof(f));
    int res = 0;
    for (int i = 0; i < n; i++)
    {
    
    
        int x;
        cin >> x;
        res ^= sg(x);
    }
    if(res) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_46126537/article/details/112178383