博弈论 -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,此时先手必败。 ①、若每一堆石子都为0,则每一堆石子数量异或值0⨁0⨁...⨁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。
②、若a1⨁a2⨁...⨁ai⨁...⨁an=0,设a1⨁a2⨁...⨁ai⨁...⨁an=x.假设x的二进制表示中最高位的1在第k位,则a1到an中必存在一个数ai,ai的第k位是1。
那
么
有
a
i
⨁
x
<
a
i
,
现
从
第
i
堆
石
子
拿
出
一
些
石
子
,
使
得
剩
下
a
i
⨁
x
个
石
子
。
\qquad那么有a_i\bigoplus x<a_i,现从第i堆石子拿出一些石子,使得剩下a_i\bigoplus x个石子。
那么有ai⨁x<ai,现从第i堆石子拿出一些石子,使得剩下ai⨁x个石子。
于
是
剩
下
的
所
有
石
子
的
异
或
值
为
:
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。
于是剩下的所有石子的异或值为:a1⨁a2⨁...⨁(ai⨁x)⨁...⨁an=a1⨁a2⨁...⨁ai⨁...⨁an⨁x=x⨁x=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。
③、若a1⨁a2⨁...⨁ai⨁...⨁an=0,则无论如何都不能保持所有堆的石子数量的异或值为0。
证
明
:
假
设
将
第
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',即没有拿石子,矛盾!
证明:假设将第i堆石子数量变为ai′,使得各堆的石子数量的异或值为0,即a1⨁a2⨁...⨁ai′⨁...⨁an=0。计算(a1⨁a2⨁...⨁ai⨁...⨁an)⨁(a1⨁a2⨁...⨁ai′⨁...⨁an)=ai⨁ai′=0。这说明ai=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,此时先手必败。 ①、若每一堆石子都为0,则每一堆石子数量异或值0⨁0⨁...⨁0=0,此时先手必败。
② 、 若 所 有 奇 数 级 台 阶 上 的 石 子 数 量 都 为 0 , 仅 剩 下 偶 数 级 台 阶 上 存 在 石 子 , 先 手 必 败 。 因 为 若 A 从 偶 数 级 台 阶 上 拿 x 个 石 子 到 奇 数 级 台 阶 上 , B 就 从 该 奇 数 级 台 阶 上 将 这 些 石 子 拿 到 下 一 个 偶 数 级 台 阶 , 那 么 B 必 然 在 A 先 到 达 地 面 。 ②、若所有奇数级台阶上的石子数量都为0,仅剩下偶数级台阶上存在石子,先手必败。\\\qquad因为若A从偶数级台阶上拿x个石子到奇数级台阶上,B就从该奇数级台阶上将这些石子拿到下一个偶数级台阶,\\\qquad那么B必然在A先到达地面。 ②、若所有奇数级台阶上的石子数量都为0,仅剩下偶数级台阶上存在石子,先手必败。因为若A从偶数级台阶上拿x个石子到奇数级台阶上,B就从该奇数级台阶上将这些石子拿到下一个偶数级台阶,那么B必然在A先到达地面。
这 样 , 我 们 就 可 以 通 过 第 一 题 的 方 法 , 控 制 奇 数 级 台 阶 上 的 石 子 的 异 或 值 为 0 。 \qquad这样,我们就可以通过第一题的方法,控制奇数级台阶上的石子的异或值为0。 这样,我们就可以通过第一题的方法,控制奇数级台阶上的石子的异或值为0。
若 对 手 从 偶 数 级 台 阶 拿 x , 我 们 就 将 这 x 个 石 子 拿 到 下 一 个 偶 数 级 台 阶 , 保 持 奇 数 级 台 阶 的 石 子 数 量 异 或 值 为 0 。 若对手从偶数级台阶拿x,我们就将这x个石子拿到下一个偶数级台阶,保持奇数级台阶的石子数量异或值为0。 若对手从偶数级台阶拿x,我们就将这x个石子拿到下一个偶数级台阶,保持奇数级台阶的石子数量异或值为0。
若 对 手 从 奇 数 级 台 阶 拿 x , 此 时 奇 数 级 台 阶 的 石 子 数 量 的 异 或 值 必 定 非 零 , 我 们 就 利 用 第 一 题 的 做 法 , 把 奇 数 级 台 阶 的 石 子 数 量 异 或 值 变 成 0 。 若对手从奇数级台阶拿x,此时奇数级台阶的石子数量的异或值必定非零,\\我们就利用第一题的做法,把奇数级台阶的石子数量异或值变成0。 若对手从奇数级台阶拿x,此时奇数级台阶的石子数量的异或值必定非零,我们就利用第一题的做法,把奇数级台阶的石子数量异或值变成0。
于
是
,
我
们
只
需
判
断
所
有
奇
数
级
台
阶
上
的
石
子
数
量
的
异
或
值
是
否
为
零
即
可
。
于是,我们只需判断所有奇数级台阶上的石子数量的异或值是否为零即可。
于是,我们只需判断所有奇数级台阶上的石子数量的异或值是否为零即可。
代码:
#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的后继 \}。 对于一个给定的有向无环图,定义关于图的每个顶点的SG函数g如下:g(x)=mex{g(y)∣y是x的后继}。 其 中 , 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。 我们把每一堆石子的初始数量看作一个状态的起始位置,以该点为起点求SG函数,设起点为x。
① 、 若 S G ( x ) ≠ 0 , 从 x 必 能 走 到 x ′ , 且 S G ( x ′ ) = 0 。 ①、若SG(x)≠0,从x必能走到x',且SG(x')=0。 ①、若SG(x)=0,从x必能走到x′,且SG(x′)=0。
② 、 若 S G ( x ) = 0 , 要 么 必 败 , 要 么 下 个 状 态 x ′ , S G ( x ′ ) ≠ 0 。 ②、若SG(x)=0,要么必败,要么下个状态x',SG(x')≠0。 ②、若SG(x)=0,要么必败,要么下个状态x′,SG(x′)=0。
示例:
若
某
堆
石
子
初
始
数
量
为
10
,
每
次
能
够
拿
2
个
或
5
个
,
则
状
态
转
移
图
如
下
:
若某堆石子初始数量为10,每次能够拿2个或5个,则状态转移图如下:
若某堆石子初始数量为10,每次能够拿2个或5个,则状态转移图如下:
首
先
终
止
状
态
标
记
为
0
,
表
示
失
败
。
接
着
倒
推
与
终
止
状
态
直
接
相
连
的
节
点
,
显
然
这
些
节
点
对
应
的
S
G
函
数
为
1
(
不
属
于
后
继
节
点
的
最
小
自
然
数
)
。
首先终止状态标记为0,表示失败。\\接着倒推与终止状态直接相连的节点,显然这些节点对应的SG函数为1(不属于后继节点的最小自然数)。
首先终止状态标记为0,表示失败。接着倒推与终止状态直接相连的节点,显然这些节点对应的SG函数为1(不属于后继节点的最小自然数)。
依 次 反 推 即 可 , 这 个 过 程 由 递 归 来 实 现 依次反推即可,这个过程由递归来实现 依次反推即可,这个过程由递归来实现
另 外 , 本 题 有 n 堆 石 子 , 就 有 n 个 状 态 转 移 图 。 另外,本题有n堆石子,就有n个状态转移图。 另外,本题有n堆石子,就有n个状态转移图。
这 与 第 一 题 相 似 , 考 虑 a 1 ⨁ a 2 ⨁ . . . ⨁ a n 是 否 为 0 即 可 。 这与第一题相似,考虑a_1\bigoplus a_2\bigoplus ...\bigoplus a_n是否为0即可。 这与第一题相似,考虑a1⨁a2⨁...⨁an是否为0即可。
只 不 过 这 里 的 a 1 应 当 取 S G ( a 1 ) , 因 为 S G ( a i ) 代 表 着 第 i 堆 石 子 的 状 态 。 只不过这里的a_1应当取SG(a_1),因为SG(a_i)代表着第i堆石子的状态。 只不过这里的a1应当取SG(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;
}