惯例先引入一些概念:(概念来自于网络)
有向图游戏:
给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。
Mex运算: 设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即: mex(S) = min{x},
x属于自然数,且x不属于S。
SG函数: 在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …,
yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即: SG(x) =
mex({SG(y1), SG(y2), …, SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。
定理:
有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。
有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。
用图片举个例子
各个结点表示一种局面,红色的数字表示该局面的SG函数的值,具体的数值大小可以由Mex函数得到,当先手面临SG函数值为0的结点时必败,否则必胜,因为只要结点值不为0就一定存在下一步可以走到结点值为0的结点。(只有存在下一步结点值为0的情况当前结点才不为0)。
来道例题理解:
给定n堆石子以及一个由k个不同正整数构成的数字集合S。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合S,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
输入格式
第一行包含整数k,表示数字集合S中数字的个数。
第二行包含k个整数,其中第i个整数表示数字集合S中的第i个数si。
第三行包含整数n。
第四行包含n个整数,其中第i个整数表示第i堆石子的数量hi。
输出格式
如果先手方必胜,则输出“Yes”。
否则,输出“No”。
数据范围
1≤n,k≤1001≤n,k≤100,
1≤si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出案例
Yes
大意:给定n堆石子,每次只能取任意堆石子里的指定个数,问先手必胜还是必败。
此时题目并不适应于之前的Nim问题模型,而由于每堆石子都有k种拿法,总共的拿取步骤将会是指数级别,所以这时需要将每堆石子的操作分别转化为SG函数模型,只要每堆石子的初始数量的SG函数值的异或和不为0即为必胜,否则必败。(原因同Nim游戏)
代码:(关键在于SG函数的写法)
#include<bits/stdc++.h>
using namespace std;
int n,m,x;
int s[110],f[110];
int sg(int x)
{
if(f[x] != -1)//记忆化搜索,保证每个结点只被搜索一次
return f[x];
unordered_set<int> mp;//每进入一个新的结点,set要被重置,因为结点的值只与该节点的下一步有关
for(int i = 0; i < m; i ++ )
{
int sum = x - s[i];
if(sum >= 0)//如果存在下一步的走法
mp.insert(sg(sum));//哈希表存下一步的SG函数值
}
for(int i = 0; ; i ++ )
{
if(!mp.count(i))//Mex函数,当前结点的SG函数值为下一步结点函数值中不存在的最小自然数
return f[x] = i;
}
}
int main()
{
int res = 0;
memset(f,-1,sizeof(f));
cin>>m;
for(int i = 0; i < m; i ++ )
cin>>s[i];
cin>>n;
while(n -- )
{
cin>>x;
res ^= sg(x);
}
if(res)
cout<<"Yes";
else
cout<<"No";
return 0;
}