【题目描述】
给定 n n n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
【输入格式】
第一行包含整数 n n n。
第二行包含 n n n个数字,其中第 i i i个数字表示第 i i i堆石子的数量。
【输出格式】
如果先手方必胜,则输出Yes
。
否则,输出No
。
【数据范围】
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1≤n≤105
1 ≤ 每 堆 石 子 数 ≤ 1 0 9 1≤每堆石子数≤10^9 1≤每堆石子数≤109
【输入样例】
2
2 3
【输出样例】
Yes
【分析】
首先给出结论:若 a 1 ∧ a 2 ∧ ⋯ ∧ a n = 0 a_1\wedge a_2 \wedge \dots \wedge a_n=0 a1∧a2∧⋯∧an=0( a i a_i ai表示第 i i i堆石子的数量),则先手必败,否则先手必胜。
在讲 N i m Nim Nim游戏之前,先了解一下什么是公平组合游戏 ( I C G ) (ICG) (ICG)。若一个游戏满足以下条件,则该游戏称为公平组合游戏:
N i m Nim Nim游戏属于公平组合游戏,但常见的棋类游戏,比如围棋就不是公平组合游戏,因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件 2 2 2和 3 3 3。
什么是先手必胜状态和先手必败状态?
为什么当 a 1 ∧ a 2 ∧ ⋯ ∧ a n = 0 a_1\wedge a_2 \wedge \dots \wedge a_n=0 a1∧a2∧⋯∧an=0时先手必败,否则先手必胜?
综上,当先手局面为 a 1 ∧ a 2 ∧ ⋯ ∧ a n = 0 a_1\wedge a_2 \wedge \dots \wedge a_n=0 a1∧a2∧⋯∧an=0时抛给后手的状态一定为 a 1 ∧ a 2 ∧ ⋯ ∧ a n ≠ 0 a_1\wedge a_2 \wedge \dots \wedge a_n≠0 a1∧a2∧⋯∧an=0,当先手局面为 a 1 ∧ a 2 ∧ ⋯ ∧ a n ≠ 0 a_1\wedge a_2 \wedge \dots \wedge a_n≠0 a1∧a2∧⋯∧an=0时抛给后手的状态一定是 a 1 ∧ a 2 ∧ ⋯ ∧ a n = 0 a_1\wedge a_2 \wedge \dots \wedge a_n=0 a1∧a2∧⋯∧an=0。所以当 a 1 ∧ a 2 ∧ ⋯ ∧ a n = 0 a_1\wedge a_2 \wedge \dots \wedge a_n=0 a1∧a2∧⋯∧an=0时先手必败,否则先手必胜。
【代码】
#include
using namespace std;
int main()
{
int n, res = 0;
cin >> n;
while (n--)
{
int x;
cin >> x;
res ^= x;
}
if (res) puts("Yes");
else puts("No");
return 0;
}
【题目描述】
现在,有一个 n n n级台阶的楼梯,每级台阶上都有若干个石子,其中第 i i i级台阶上有 a i a_i ai个石子 ( i ≥ 1 ) (i≥1) (i≥1)。
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。
已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
【输入格式】
第一行包含整数 n n n。
第二行包含 n n n个整数,其中第 i i i个整数表示第 i i i级台阶上的石子数 a i a_i ai。
【输出格式】
如果先手方必胜,则输出Yes
。
否则,输出No
。
【数据范围】
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1≤n≤105
1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1≤ai≤109
【输入样例】
3
2 1 3
【输出样例】
Yes
【分析】
结论:当奇数级阶梯的石子数量异或值为 0 0 0,即 a 1 ∧ a 3 ∧ ⋯ ∧ a 2 n − 1 = 0 a_1\wedge a_3\wedge \dots \wedge a_{2n-1}=0 a1∧a3∧⋯∧a2n−1=0时,先手必败,否则先手必胜。证明思路同经典 N i m Nim Nim游戏相似。
【代码】
#include
using namespace std;
int main()
{
int n, res = 0;
cin >> n;
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
if (i % 2) res ^= x;
}
if (res) puts("Yes");
else puts("No");
return 0;
}
【题目描述】
给定 n n n堆石子以及一个由 k k k个不同正整数构成的数字集合 S S S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S S S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
【输入格式】
第一行包含整数 k k k,表示数字集合 S S S中数字的个数。
第二行包含 k k k个整数,其中第 i i i个整数表示数字集合 S S S中的第 i i i个数 s i s_i si。
第三行包含整数 n n n。
第四行包含 n n n个整数,其中第 i i i个整数表示第 i i i堆石子的数量 h i h_i hi。
【输出格式】
如果先手方必胜,则输出Yes
。
否则,输出No
。
【数据范围】
1 ≤ n , k ≤ 100 1≤n,k≤100 1≤n,k≤100
1 ≤ s i , h i ≤ 10000 1≤s_i,h_i≤10000 1≤si,hi≤10000
【输入样例】
2
2 5
3
2 4 7
【输出样例】
Yes
【分析】
(1)Mex运算:
设 S S S表示一个非负整数集合,定义 m e x ( S ) mex(S) mex(S)为求出不属于集合 S S S的最小非负整数运算,即:
m e x ( S ) = m i n { x } mex(S)=min\left\{x\right\} mex(S)=min{x};
例如: S = { 0 , 1 , 2 , 4 } S=\left\{0,1,2,4\right\} S={0,1,2,4},那么 m e x ( S ) = 3 mex(S)=3 mex(S)=3。
(2)SG函数
在有向图游戏中,对于每个节点 x x x,设从 x x x出发共有 k k k条有向边,分别到达节点 y 1 , y 2 , … , y k y_1,y_2,\dots ,y_k y1,y2,…,yk,定义 S G ( x ) SG(x) SG(x)为 x x x的后继节点 y 1 , y 2 , … , y k y_1,y_2,\dots ,y_k y1,y2,…,yk的 S G SG SG函数值构成的集合执行 m e x mex mex运算的结果,即:
S G ( x ) = m e x ( { S G ( y 1 ) , S G ( y 2 ) , … , S G ( y k ) } ) SG(x)=mex(\left\{SG(y_1),SG(y_2),\dots ,SG(y_k)\right\}) SG(x)=mex({SG(y1),SG(y2),…,SG(yk)})
特别地,整个有向图游戏 G G G的 S G SG SG函数值被定义为有向图游戏起点 s s s的 S G SG SG函数值,即: S G ( G ) = S G ( s ) SG(G)=SG(s) SG(G)=SG(s)。
定义终点的 S G SG SG函数值为 0 0 0。
(3)有向图游戏的和
设 G 1 , G 2 , … , G m G_1,G_2,\dots ,G_m G1,G2,…,Gm是 m m m个有向图游戏。定义有向图游戏 G , G, G,他的行动规则是任选某个有向图游戏 G i G_i Gi,并在 G i G_i Gi上行动一步, G G G被称为有向图游戏 G 1 , G 2 , … , G m G_1,G_2,\dots ,G_m G1,G2,…,Gm的和。
有向图游戏的和的 S G SG SG函数值等于它包含的各个子游戏 S G SG SG函数的异或和,即:
S G ( G ) = S G ( G 1 ) ∧ S G ( G 2 ) ∧ ⋯ ∧ S G ( G m ) SG(G)=SG(G_1)\wedge SG(G_2)\wedge \dots \wedge SG(G_m) SG(G)=SG(G1)∧SG(G2)∧⋯∧SG(Gm)。
(4)结论
例子:假设有一堆石子,石子数量为 10 10 10,每次可以拿走 2 2 2个或 5 5 5个,那么其有向图如下图所示:
红色标注的数字为该结点的 S G SG SG函数值,由于起点 10 10 10的 S G SG SG函数值不为 0 0 0,因此该游戏局面为先手必胜状态。
【代码】
#include
#include
#include
#include
using namespace std;
const int N = 110, M = 10010;
int s[N], f[M];
int n, k;
//记忆化搜索保存各结点的SG值
int sg(int x)
{
if (~f[x]) return f[x];
unordered_set<int> st;//保存x能到达的后继结点的SG值
//枚举x能到达的所有状态
for (int i = 0; i < k; i++)
if (s[i] <= x) st.insert(sg(x - s[i]));
//mex操作,找出x后继结点中未出现过的最小的非负整数
for (int i = 0; ; i++)
if (!st.count(i))
return f[x] = i;
}
int main()
{
cin >> k;
for (int i = 0; i < k; i++) cin >> s[i];
memset(f, -1, sizeof f);
int res = 0;
cin >> n;
while (n--)
{
int x;
cin >> x;
res ^= sg(x);//对每个有向图的起始结点的SG值进行异或运算
}
if (res) puts("Yes");
else puts("No");
return 0;
}
【题目描述】
给定 n n n堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为 0 0 0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
【输入格式】
第一行包含整数 n n n。
第二行包含 n n n个整数,其中第 i i i个整数表示第 i i i堆石子的数量 a i a_i ai。
【输出格式】
如果先手方必胜,则输出Yes
。
否则,输出No
。
【数据范围】
1 ≤ n , a i ≤ 100 1≤n,a_i≤100 1≤n,ai≤100
【输入样例】
2
2 3
【输出样例】
Yes
【分析】
相比于集合 N i m Nim Nim游戏,这里的每一堆可以变成小于原来那堆的任意大小的两堆。
即 a [ i ] a[i] a[i]可以拆分成 ( b [ i ] , b [ j ] ) (b[i],b[j]) (b[i],b[j]),为了避免重复规定 b [ i ] ≥ b [ j ] b[i]\geq b[j] b[i]≥b[j],即: a [ i ] > b [ i ] ≥ b [ j ] a[i]>b[i]\geq b[j] a[i]>b[i]≥b[j]。
相当于一个局面拆分成了两个局面,由 S G SG SG函数理论,多个独立局面的 S G SG SG值,等于这些局面 S G SG SG值的异或和。
因此需要存储的状态就是 s g ( b [ i ] ) ∧ s g ( b [ j ] ) sg(b[i])\wedge sg(b[j]) sg(b[i])∧sg(b[j])。
【代码】
#include
#include
#include
#include
using namespace std;
const int N = 110;
int f[N];
int n;
int sg(int x)
{
if (~f[x]) return f[x];
unordered_set<int> st;
//枚举x可变成的所有状态(i, j),防止重复j只用枚举到i即可
for (int i = 0; i < x; i++)
for (int j = 0; j <= i; j++)
st.insert(sg(i) ^ sg(j));
//mex操作
for (int i = 0; ; i++)
if (!st.count(i))
return f[x] = i;
}
int main()
{
cin >> n;
memset(f, -1, sizeof f);
int res = 0;
while (n--)
{
int x;
cin >> x;
res ^= sg(x);
}
if (res) puts("Yes");
else puts("No");
return 0;
}