(AcWing)集合-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<algorithm>
#include<unordered_set>
#include<cstring>
using namespace std;

const int N = 110, M = 10010;

int s[N],f[M];  //s用来存储数字集合,f用来存储各状态的sg
int n,m;

int sg(int num)
{
    //如果当前这个数的sg不为-1,说明已被计算过,则直接返回
    if(f[num]!=-1) return f[num];
    
    //定义哈希表,防止出现重复的数字
    unordered_set<int> S;
    
    //对该堆石子进行判断,是否可以拿取集合内的数量的石子,若可以,则将拿去后的状态进行sg(即剩余石子的数量)
    for(int i=0;i<n;i++) if(num>=s[i]) S.insert(sg(num-s[i]));
    
    //对哈希表进行查找,每次将不存在集合中的最小的自然数赋予f[num](即Mex运算),并返回f[num];
    for(int i=0; ;i++) if(!S.count(i)) return f[num] = i;
}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>s[i];
    
    memset(f,-1,sizeof f);  //初始化f数组,每个值均为-1
    
    int res = 0;
    cin>>m;
    
    //若起点的sg(即初始石子堆的sg)均不为0,则先手必胜
    //设终点的sg为0,若起点的sg不为0,则可以进行一系列的操作后到0,在这一系列操作中有到0的,有不到0的,不到0的就是获胜的操作
    //若起点为0,0是最小的自然数,由Mex运算可知,0无法通过任何操作变成非零数,即这就是先手必败
    for(int i=0;i<m;i++)
    {
        int num;
        cin>>num;
        res^=sg(num);
    }
    
    if(res) cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
    
    return 0;
}

猜你喜欢

转载自blog.csdn.net/GF0919/article/details/132001420