893. 集合-Nim游戏

893. 集合-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
公式推导:

将每一个h[i]h[i]的所有方案看做是一张有向图,例

若S=[2,5],h=10,则有如下展开形式:11.png

**mex():**设集合S是一个非负整数集合,定义mex(S)为求出不属于S的最小非负整数的运算,即:mes(S)=min[x],其中x属于自然数,且x不属于S(用人话说就是不存在S集合中的数中,最小的那个数)

SG():在有向图中,对于每个节点x,设从x出发共有k条有向边,分别达到节点y1,y2……yk,定义SG(x)为x的后继节点的SG值构成的集合执行mex()运算后的值
即:SG(x)=mex(SG(y1),SG(y2)…SG(yk));(用人话说就是比后继节点的SG都小的值)
特别的整个图G的SG值被定义为起点s的SG值,即SG(G)=SG(s)
上图标红的值就是每一个节点的SG值
性质:1.SG(i)=k,则i最大能到达的点的SG值为k−1。
2.非0可以走向0
3.0只能走向非0

定理:
对于一个图G,如果SG(G)!=0,则先手必胜,反之必败

证明:
若SG(G)=!0,
1.根据性质2,先手必可以走向0,
2.因此留给后手的是0,根据性质3,后手只能走向非0
3.以此类推,后手始终无法走向0,后手永远处于非0,当先手到达终点的0时,先手获胜
(由此我们可以知道,有些事是命中注定的~~~)
反之同理,必败

定理:
对于n个图,如果SG(G1) ^ SG(G2) ^ … SG(Gn)!=0,则先手必胜,反之必败

证明(类似与Nim游戏):
①当SG(Gi)=0 时 ,xor=0 , 显然先手必败
(PS:结束状态必是状态①,但状态①不一定是结束状态)

②当xor=x!=0 时,因为肯定存在一个SG(xi)^x <SG(xi),而根据SG()的性质1可知,SG(k)可以走到0−k−1的任何一个状态,
因此,必定可以从SG(xi)−>SG(xi)^x , 于是使得xor=0

③当xor=0时,当移动任何一个节点时,对应的SG值必然减小,可以证明:xor!=0
下证:xor!=0
假设xor=0,则说明移动的那个节点的值并没有变化,即从SG(k)变成了k,但是这与SG函数的性质1相矛盾,因此不成立

证得:若先手面对的状态是xor!=0,则先手方总能使xor=0,即使后手面对的永远是必败态直到结束状态①,因此先手必胜!
反之,必败!

代码:
#include <bits/stdc++.h>
using namespace std;

const int N = 110, M = 1e4 + 10;

typedef long long LL;

int n, k;
int s[N], f[M];

int sg(int x)
{
    if (f[x] != -1)
        return f[x];

    unordered_set<int> S;
    for (int i = 0; i < k; i++)
    {
        int temp = s[i];
        if (x >= temp)
            S.insert(sg(x - temp));
    }

    for (int i = 0;; i++)
    {
        if (!S.count(i))
            return f[x] = i;
    }
}

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

    cin >> n;

    memset(f, -1, sizeof f);

    int res = 0;
    for (int i = 0; i < n; i++)
    {
        int temp;
        cin >> temp;
        res ^= sg(temp);
    }

    if (res)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追寻远方的人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值