拆分Nim游戏

  • Post author:
  • Post category:其他




拆分Nim游戏

给定n堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。

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



输入格式

第一行包含整数n。

第二行包含n个整数,其中第i个整数表示第i堆石子的数量ai。



输出格式

如果先手方必胜,则输出“Yes”。

否则,输出“No”。



数据范围

1≤n,ai≤100



输入样例

2

2 3



输出样例

Yes



题目分析

在有向图游戏中,对于每个节点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)。

Mex运算

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

mex(S) = min{x}, x属于自然数,且x不属于S

对于该题可能存在多种上述的不同的有向图,即多个SG函数的值。

由于在该题中我们每一堆石子均可操作,且有集合S内元素的情况。因此我们可以得到每一堆地SG值而后对每一堆地SG值进行异或计算。结果为0则先手必败,否则为先手必胜。

使用异或的结果来判断的原因参考经典Nim游戏的结论·。

对于拆封Nim游戏

首先由题目可知对于每次操作我们可以拿去一堆石子,并拿回另外两堆石子,且石子小于被拿走的堆。

因此可以使用sg函数对每一堆进行计算,(注:此处对于每一堆的拆分时,我们需要计算后续的每种拆分的

可能,同时由于计算中可能出现需要计算大量重复的堆数的sg值,因此此处可以使用记忆化搜索记录已经计算

过的特定堆数的sg值,以优化计算时间。)

此处拆分某一堆可能存在如下图情况。

设某一堆的数量为5

则可以得到情况1

在这里插入图片描述

当然也存在另一种的划分情况

如下图

在这里插入图片描述

除上述情况还有其他情况存在,需要计算其他情况

因此这样的情况,同时对于5有其他的拆分请况,我们需要计算出所有的请情况的sg值,取除了

集合中sg值外的最小值则为答案

代码如下

#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_set>
using namespace std;
const int N=110;
int n,sg[N],ans;
int getsg(int t)
{
    if(sg[t]!=-1)return sg[t];
    unordered_set<int> map;
    for(int i=0;i<t;i++)
    for(int j=i;j>=0;j--)
        map.insert(getsg(i)^getsg(j));
    for(int i=0;;i++)if(!map.count(i))return sg[t]=i;
}
int main()
{
    cin>>n;
    memset(sg,-1,sizeof(sg));
    for(int i=0;i<n;i++)
    {
        int a;
        cin>>a;
        ans^=getsg(a);
    }
    ans?cout<<"Yes":cout<<"No";
    return 0; 
}



版权声明:本文为qq_44423125原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。