集合-Nim游戏

集合-Nim游戏

给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜

ICG游戏

  1. 游戏有两个人参与,二者轮流做出决策。且这两个人的决策都对自己最有利。

  2. 当有一人无法做出决策时游戏结束,无法做出决策的人输。无论二者如何做出决策,游戏可以在有限步内结束。

  3. 游戏中的同一个状态不可能多次抵达。且游戏不会有平局出现。任意一个游戏者在某一确定状态可以作出的决策集合只与当前的状态有关,而与游戏者无关。

满足上述条件的问题我们称之为ICG游戏,ICG游戏属于组合游戏

最典型的nim游戏,就是一种ICG游戏

必胜态与必败态

定义:

  1. 无法移动的状态为必败态
  2. 可以移动到必胜态为必败态
  3. 所有移动都会进入必败态为必胜态

DAG(有向无环图)中的博弈

给定一张有向无环图,在起始定点有一枚棋子,两个顶尖聪明的人交替移动这枚棋子,不能移动的人算输。
所有ICG游戏可以抽象为DAG博弈,即把初始局面作为顶点,从一个状态到另一个状态连边。

mex运算

这是一种集合运算
定义:
最小的不属于集合的非负整数
例如\(mex\left\{ 1,2,3 \right\}=0, mex\left\{0,2\right\}=1,mex\left\{\right\}=0\)

SG(Sprague-Grundy)函数

对于给定的有向无环图,定义每个点的SG值为$$SG(x) = mex\left\{SG(y)\ |\ x\ can\ goto\ y\right\}$$
性质:

  1. 所有终点的SG值为0
  2. \(SG(x) = 0\)时,该节点为必败态
  3. \(SG(x) \neq 0\),该节点为必胜态
  4. \(SG(x) = k\)时,后继状态中有\(SG(y)=1...k-1\)

解决问题

有了理论基础后我们可以开始分析这个问题
image
于是我们的问题就变成了一个Nim游戏,Nim游戏的解法在这https://www.cnblogs.com/tian-zai/p/16542854.html

所以我们的最终解法就是先算出每堆石子的SG值,然后求其异或和,若不为零则先手必胜

值得一提的是,在确定了某堆石子数目和集合s中的值后我们DAG中的关于该值的分支就不会再改变了,无论其再哪个堆的DAG中,因此我们可以将这个值记录下来,避免在运算过程当中的重复计算,也就是记忆化搜索,是将本题的时间复杂度从指数降为线性的关键。

点击查看代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<set>

using namespace std;

const int N=110,M=10010;
int n,m;
int f[M],s[N];//s存储的是可供选择的集合,f存储的是所有可能出现过的情况的sg值

int sg(int x)
{
    if(f[x]!=-1) return f[x];
    //因为取石子数目的集合是已经确定了的,所以每个数的sg值也都是确定的,如果存储过了,直接返回即可
    set<int> S;
    //set代表的是有序集合(注:因为在函数内部定义,所以下一次递归中的S不与本次相同)
    for(int i=0;i<m;i++)
    {
        int sum=s[i];
        if(x>=sum) S.insert(sg(x-sum));
        //先延伸到终点的sg值后,再从后往前排查出所有数的sg值
    }

    for(int i=0;;i++)
    //循环完之后可以进行选出最小的没有出现的自然数的操作
     if(!S.count(i))
      return f[x]=i;
}

int main()
{
    cin>>m;
    for(int i=0;i<m;i++)
    cin>>s[i];

    cin>>n;
    memset(f,-1,sizeof(f));//初始化f均为-1,方便在sg函数中查看x是否被记录过

    int res=0;
    for(int i=0;i<n;i++)
    {
        int x;
        cin>>x;
        res^=sg(x);
        //观察异或值的变化,基本原理与Nim游戏相同
    }

    if(res) printf("Yes");
    else printf("No");

    return 0;
}



posted @ 2022-08-02 11:37  天仔  阅读(173)  评论(0编辑  收藏  举报