博弈论 -Nim游戏(台阶 + SG函数 + 集合 + 拆分)

博弈论 -Nim游戏(台阶 + 集合 + 拆分)

1、Nim游戏

给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

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

输入格式
第一行包含整数n。

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

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

否则,输出“No”。

数据范围
1≤n≤105,
1≤每堆石子数≤109

输入样例:
2
2 3
输出样例:
Yes

分析:

假 设 n 堆 石 子 的 石 子 数 量 分 别 为 : a 1 , a 2 , . . . , a n 。 假设n堆石子的石子数量分别为:a_1,a_2,...,a_n。 n:a1,a2,...,an

① 、 若 每 一 堆 石 子 都 为 0 , 则 每 一 堆 石 子 数 量 异 或 值 0 ⨁ 0 ⨁ . . . ⨁ 0 = 0 , 此 时 先 手 必 败 。 ①、若每一堆石子都为0,则每一堆石子数量异或值0\bigoplus0\bigoplus...\bigoplus0=0,此时先手必败。 000...0=0

② 、 若 a 1 ⨁ a 2 ⨁ . . . ⨁ a i ⨁ . . . ⨁ a n ≠ 0 , 设 a 1 ⨁ a 2 ⨁ . . . ⨁ a i ⨁ . . . ⨁ a n = x . 假 设 x 的 二 进 制 表 示 中 最 高 位 的 1 在 第 k 位 , 则 a 1 到 a n 中 必 存 在 一 个 数 a i , a i 的 第 k 位 是 1 。 ②、若a_1\bigoplus a_2\bigoplus...\bigoplus a_i\bigoplus ...\bigoplus a_n≠0,设a_1\bigoplus a_2\bigoplus...\bigoplus a_i\bigoplus ...\bigoplus a_n=x.\\\qquad假设x的二进制表示中最高位的1在第k位,则a_1到a_n中必存在一个数a_i,a_i的第k位是1。 a1a2...ai...an=0a1a2...ai...an=x.x1ka1anaiaik1
那 么 有 a i ⨁ x < a i , 现 从 第 i 堆 石 子 拿 出 一 些 石 子 , 使 得 剩 下 a i ⨁ x 个 石 子 。 \qquad那么有a_i\bigoplus x<a_i,现从第i堆石子拿出一些石子,使得剩下a_i\bigoplus x个石子。 aix<aii使aix
于 是 剩 下 的 所 有 石 子 的 异 或 值 为 : a 1 ⨁ a 2 ⨁ . . . ⨁ ( a i ⨁ x ) ⨁ . . . ⨁ a n = a 1 ⨁ a 2 ⨁ . . . ⨁ a i ⨁ . . . ⨁ a n ⨁ x = x ⨁ x = 0 。 \qquad于是剩下的所有石子的异或值为:\\\qquad a_1\bigoplus a_2\bigoplus...\bigoplus (a_i\bigoplus x)\bigoplus ...\bigoplus a_n=a_1\bigoplus a_2\bigoplus...\bigoplus a_i\bigoplus ...\bigoplus a_n\bigoplus x=x\bigoplus x=0。 a1a2...(aix)...an=a1a2...ai...anx=xx=0

③ 、 若 a 1 ⨁ a 2 ⨁ . . . ⨁ a i ⨁ . . . ⨁ a n = 0 , 则 无 论 如 何 都 不 能 保 持 所 有 堆 的 石 子 数 量 的 异 或 值 为 0 。 ③、若a_1\bigoplus a_2\bigoplus...\bigoplus a_i\bigoplus ...\bigoplus a_n=0,则无论如何都不能保持所有堆的石子数量的异或值为0。 a1a2...ai...an=00
证 明 : 假 设 将 第 i 堆 石 子 数 量 变 为 a i ′ , 使 得 各 堆 的 石 子 数 量 的 异 或 值 为 0 , 即 a 1 ⨁ a 2 ⨁ . . . ⨁ a i ′ ⨁ . . . ⨁ a n = 0 。 计 算 ( a 1 ⨁ a 2 ⨁ . . . ⨁ a i ⨁ . . . ⨁ a n ) ⨁ ( a 1 ⨁ a 2 ⨁ . . . ⨁ a i ′ ⨁ . . . ⨁ a n ) = a i ⨁ a i ′ = 0 。 这 说 明 a i = a i ′ , 即 没 有 拿 石 子 , 矛 盾 ! \qquad证明:假设将第i堆石子数量变为a_i',使得各堆的石子数量的异或值为0,\\\qquad即a_1\bigoplus a_2\bigoplus...\bigoplus a_i'\bigoplus ...\bigoplus a_n=0。\\\qquad计算(a_1\bigoplus a_2\bigoplus...\bigoplus a_i\bigoplus ...\bigoplus a_n)\bigoplus (a_1\bigoplus a_2\bigoplus...\bigoplus a_i'\bigoplus ...\bigoplus a_n)=a_i\bigoplus a_i'=0。\\\qquad这说明a_i=a_i',即没有拿石子,矛盾! iai使0a1a2...ai...an=0(a1a2...ai...an)(a1a2...ai...an)=aiai=0ai=ai

于 是 , 只 要 先 手 的 情 况 下 , 各 堆 石 子 数 量 的 异 或 值 不 为 0 , 我 们 可 以 拿 一 些 石 子 使 得 异 或 值 为 0 , 直 到 所 有 石 子 被 拿 空 。 于是,只要先手的情况下,各堆石子数量的异或值不为0,我们可以拿一些石子使得异或值为0,直到所有石子被拿空。 0使0

而 轮 到 对 手 时 , 始 终 是 异 或 值 为 零 的 情 况 , 对 手 拿 完 后 , 均 会 使 得 异 或 值 非 零 。 而轮到对手时,始终是异或值为零的情况,对手拿完后,均会使得异或值非零。 使

因 此 , 只 需 判 断 各 堆 石 子 的 异 或 值 是 否 为 零 即 可 。 因此,只需判断各堆石子的异或值是否为零即可。

代码:

#include<iostream>

using namespace std;

int main()
{
    int n;
    cin>>n;
    
    int res=0;
    while(n--)
    {
        int x;
        cin>>x;
        res^=x;
    }
    
    if(res) puts("Yes");
    else puts("No");
    
    return 0;
}

2、台阶-Nim游戏

现在,有一个n级台阶的楼梯,每级台阶上都有若干个石子,其中第i级台阶上有ai个石子(i≥1)。

两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。

已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。

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

输入格式
第一行包含整数n。

第二行包含n个整数,其中第i个整数表示第i级台阶上的石子数ai。

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

否则,输出“No”。

数据范围
1≤n≤105,
1≤ai≤109

输入样例:
3
2 1 3
输出样例:
Yes

分析:

假 设 n 堆 石 子 的 石 子 数 量 分 别 为 : a 1 , a 2 , . . . , a n 。 假设n堆石子的石子数量分别为:a_1,a_2,...,a_n。 n:a1,a2,...,an

① 、 若 每 一 堆 石 子 都 为 0 , 则 每 一 堆 石 子 数 量 异 或 值 0 ⨁ 0 ⨁ . . . ⨁ 0 = 0 , 此 时 先 手 必 败 。 ①、若每一堆石子都为0,则每一堆石子数量异或值0\bigoplus0\bigoplus...\bigoplus0=0,此时先手必败。 000...0=0

② 、 若 所 有 奇 数 级 台 阶 上 的 石 子 数 量 都 为 0 , 仅 剩 下 偶 数 级 台 阶 上 存 在 石 子 , 先 手 必 败 。 因 为 若 A 从 偶 数 级 台 阶 上 拿 x 个 石 子 到 奇 数 级 台 阶 上 , B 就 从 该 奇 数 级 台 阶 上 将 这 些 石 子 拿 到 下 一 个 偶 数 级 台 阶 , 那 么 B 必 然 在 A 先 到 达 地 面 。 ②、若所有奇数级台阶上的石子数量都为0,仅剩下偶数级台阶上存在石子,先手必败。\\\qquad因为若A从偶数级台阶上拿x个石子到奇数级台阶上,B就从该奇数级台阶上将这些石子拿到下一个偶数级台阶,\\\qquad那么B必然在A先到达地面。 0AxBBA

这 样 , 我 们 就 可 以 通 过 第 一 题 的 方 法 , 控 制 奇 数 级 台 阶 上 的 石 子 的 异 或 值 为 0 。 \qquad这样,我们就可以通过第一题的方法,控制奇数级台阶上的石子的异或值为0。 0

若 对 手 从 偶 数 级 台 阶 拿 x , 我 们 就 将 这 x 个 石 子 拿 到 下 一 个 偶 数 级 台 阶 , 保 持 奇 数 级 台 阶 的 石 子 数 量 异 或 值 为 0 。 若对手从偶数级台阶拿x,我们就将这x个石子拿到下一个偶数级台阶,保持奇数级台阶的石子数量异或值为0。 xx0

若 对 手 从 奇 数 级 台 阶 拿 x , 此 时 奇 数 级 台 阶 的 石 子 数 量 的 异 或 值 必 定 非 零 , 我 们 就 利 用 第 一 题 的 做 法 , 把 奇 数 级 台 阶 的 石 子 数 量 异 或 值 变 成 0 。 若对手从奇数级台阶拿x,此时奇数级台阶的石子数量的异或值必定非零,\\我们就利用第一题的做法,把奇数级台阶的石子数量异或值变成0。 x0

于 是 , 我 们 只 需 判 断 所 有 奇 数 级 台 阶 上 的 石 子 数 量 的 异 或 值 是 否 为 零 即 可 。 于是,我们只需判断所有奇数级台阶上的石子数量的异或值是否为零即可。
代码:

#include<iostream>

using namespace std;

int main()
{
    int n;
    cin>>n;
    int res=0;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        if(i&1) res^=x;
    }
    
    if(res) puts("Yes");
    else puts("No");
    
    return 0;
}

3、SG函数

S G 函 数 : SG函数: SG 对 于 一 个 给 定 的 有 向 无 环 图 , 定 义 关 于 图 的 每 个 顶 点 的 S G 函 数 g 如 下 : g ( x ) = m e x { g ( y ) ∣ y 是 x 的 后 继 } 。 对于一个给定的有向无环图,定义关于图的每个顶点的SG函数g如下:g(x)=mex\{ g(y) | y是x的后继 \}。 SGgg(x)=mex{g(y)yx} 其 中 , m e x 是 求 不 属 于 该 集 合 的 最 小 自 然 数 。 其中,mex是求不属于该集合的最小自然数。 mex


4、集合-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

分析:

我 们 把 每 一 堆 石 子 的 初 始 数 量 看 作 一 个 状 态 的 起 始 位 置 , 以 该 点 为 起 点 求 S G 函 数 , 设 起 点 为 x 。 我们把每一堆石子的初始数量看作一个状态的起始位置,以该点为起点求SG函数,设起点为x。 SGx

① 、 若 S G ( x ) ≠ 0 , 从 x 必 能 走 到 x ′ , 且 S G ( x ′ ) = 0 。 ①、若SG(x)≠0,从x必能走到x',且SG(x')=0。 SG(x)=0xxSG(x)=0

② 、 若 S G ( x ) = 0 , 要 么 必 败 , 要 么 下 个 状 态 x ′ , S G ( x ′ ) ≠ 0 。 ②、若SG(x)=0,要么必败,要么下个状态x',SG(x')≠0。 SG(x)=0xSG(x)=0

示例:

若 某 堆 石 子 初 始 数 量 为 10 , 每 次 能 够 拿 2 个 或 5 个 , 则 状 态 转 移 图 如 下 : 若某堆石子初始数量为10,每次能够拿2个或5个,则状态转移图如下: 1025
在这里插入图片描述
首 先 终 止 状 态 标 记 为 0 , 表 示 失 败 。 接 着 倒 推 与 终 止 状 态 直 接 相 连 的 节 点 , 显 然 这 些 节 点 对 应 的 S G 函 数 为 1 ( 不 属 于 后 继 节 点 的 最 小 自 然 数 ) 。 首先终止状态标记为0,表示失败。\\接着倒推与终止状态直接相连的节点,显然这些节点对应的SG函数为1(不属于后继节点的最小自然数)。 0SG1()

依 次 反 推 即 可 , 这 个 过 程 由 递 归 来 实 现 依次反推即可,这个过程由递归来实现

另 外 , 本 题 有 n 堆 石 子 , 就 有 n 个 状 态 转 移 图 。 另外,本题有n堆石子,就有n个状态转移图。 nn

这 与 第 一 题 相 似 , 考 虑 a 1 ⨁ a 2 ⨁ . . . ⨁ a n 是 否 为 0 即 可 。 这与第一题相似,考虑a_1\bigoplus a_2\bigoplus ...\bigoplus a_n是否为0即可。 a1a2...an0

只 不 过 这 里 的 a 1 应 当 取 S G ( a 1 ) , 因 为 S G ( a i ) 代 表 着 第 i 堆 石 子 的 状 态 。 只不过这里的a_1应当取SG(a_1),因为SG(a_i)代表着第i堆石子的状态。 a1SG(a1)SG(ai)i

这 里 每 一 堆 石 子 的 状 态 图 用 哈 希 表 来 存 。 这里每一堆石子的状态图用哈希表来存。

代码:

#include<iostream>
#include<cstring>
#include<unordered_set>

using namespace std;

const int N = 110 , M = 10010;

int n,k,a[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++)
        if(x-a[i]>=0) 
            S.insert(sg(x-a[i]));
            
    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>>a[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) puts("Yes");
    else puts("No");
    
    return 0;
}

5、拆分-Nim游戏

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

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

输入格式
第一行包含整数n。

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

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

否则,输出“No”。

数据范围
1≤n,ai≤100

输入样例:
2
2 3
输出样例:
Yes

分析:

本 题 与 第 四 题 相 似 , 把 每 一 堆 石 子 看 作 一 个 状 态 图 , 只 不 过 转 移 时 一 堆 会 分 成 两 堆 , 本题与第四题相似,把每一堆石子看作一个状态图,只不过转移时一堆会分成两堆,

两 堆 的 状 态 应 当 是 两 堆 石 子 数 量 的 异 或 值 。 两堆的状态应当是两堆石子数量的异或值。

代码:

#include<iostream>
#include<cstring>
#include<unordered_set>

using namespace std;

const int N=110;

int f[N];

int sg(int x)
{
    if(f[x]!=-1) return f[x];
    
    unordered_set<int> S;
    for(int i=0;i<x;i++)
        for(int j=0;j<=i;j++)
            S.insert(sg(i)^sg(j));
            
    for(int i=0;;i++)
        if(!S.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) puts("Yes");
    else puts("No");
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值