概念
1、平等组合游戏
- 两人游戏。
- 两人轮流走步。
- 有一个状态集,而且通常是有限的。
- 有一个终止状态,到达终止状态后游戏结束。
- 游戏可以在有限的步数内结束。
- 规定好了哪些状态转移是合法的。
- 所有规定对于两人是一样的。
2、N状态(必胜状态),P状态(必败状态)
- 所有的终止状态都为P状态
- 对于任意的N状态,存在至少一条路径可以转移到P状态
- 对于任意的P状态,只能转移到N状态
Nim游戏
给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 n。
第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量。
输出格式
如果先手方必胜,则输出 Yes。
否则,输出 No。
数据范围
1≤n≤1e5,
1≤每堆石子数≤1e9
台阶-Nim游戏
现在,有一个 𝑛 级台阶的楼梯,每级台阶上都有若干个石子,其中第 𝑖 级台阶上有 𝑎𝑖 个石子(𝑖≥1)。
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。
已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 𝑛。第二行包含 𝑛 个整数,其中第 𝑖 个整数表示第 𝑖 级台阶上的石子数 𝑎𝑖。
输出格式
如果先手方必胜,则输出 Yes。否则,输出 No。
所以只要判断初始状态奇数台阶上石子的异或和是否为0;
SG函数和SG定义
SG函数:sg(x)=mex{ sg(y) | y是x的后继 }
mex函数:当前 最小的不属于这个集合的非负整数
用mex函数推导必胜态和必败态
例
给定 𝑛 堆石子以及一个由 𝑘 个不同正整数构成的数字集合 𝑆。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 𝑆,最后无法进行操作的人视为失败。
集合-Nim游戏
给定 𝑛 堆石子以及一个由 𝑘 个不同正整数构成的数字集合 S𝑆。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 𝑆,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数 𝑘,表示数字集合 𝑆 中数字的个数。
第二行包含 𝑘 个整数,其中第 𝑖 个整数表示数字集合 S𝑆 中的第 𝑖 个数 𝑠𝑖。
第三行包含整数 𝑛。
第四行包含 𝑛 个整数,其中第 𝑖 个整数表示第 i𝑖 堆石子的数量 ℎ𝑖。
输出格式
如果先手方必胜,则输出
Yes
。否则,输出
No
。数据范围
1≤n,k≤1001≤𝑛,𝑘≤100,
1≤si,hi≤10000
首先需要知道:有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数的异或和,即:
SG(G)=SG(G1)xorSG(G2)xor···xor SG(Gm)
代码实现
int s[N],f[N];
unordered_set<int>S;
int k,n;
int res=0;
int sg(int x){
if(f[x]!=-1) return f[x];
//记忆化搜索,如果已经计算过,就直接输出
unordered_set<int>S;
for(int i=0;i<k;i++)
{
if(x>=s[i]) S.insert(sg(x-s[i]));
//递归
}
for(int i=0;;i++)
{
if(!S.count(i)) return f[x]=i;
}
//寻找最小并不在原集合的数
}
void solve()
{
memset(f,-1,sizeof f);
cin>>k;
for(int i=0;i<k;i++) cin>>s[i];
cin>>n;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
res^=sg(x);
}
if(res) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}