集合 SG 函数
给定 堆石子以及一个由 个不同正整数构成的数字集合 。
现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 ,最后无法进行操作的人视为失败。
问如果两人都采用最优策略,先手是否必胜。
思路
前置知识: Mex 运算: 设 S 表示一个非负整数集合.定义为求出不属于集合的最小非负整数运算,即: ; 例如:,那么; SG 函数: 在有向图游戏中,对于每个节点,设从出发共有条有向边,分别到达节点,定义的后记节点 的函数值构成的集合在执行运算的结果,即: 特别地,整个有向图游戏的函数值被定义为有向图游戏起点的函数值,即 . 有向图游戏的和 设是个有向图游戏.定义有向图游戏,他的行动规则是任选某个有向图游戏,并在上行动一步.被称为有向图游戏的和. 有向图游戏的和的函数值等于它包含的各个子游戏函数的异或和,即:
假设取石子的集合 , 仅有一堆石子, 石子数为 :
求出整个图的 SG 函数值(红色即是该节点的 SG 值)
可以发现:当 SG 值为 0 时, 从该点先手一定会输。
再根据有向图游戏的和性质, 若所有堆石子数异或后的结果为 0, 则先手必输。
计算所有 SG 函数时使用记忆化搜索, 也算枚举了所有选择的可能, 相当暴力的做法, 复杂度为指数级别。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>
using namespace std;
const int N = 1e2 + 10, M = 1e5;
int s[N], f[M];
int k,n;
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;
}
int main()
{
memset(f, -1, sizeof f);
cin >> k;
for(int i = 0; i < k; i++) cin >> s[i];
cin >> n;
int res = 0;
for(int i = 0; i < n; i++)
{
int x;
cin >> x;
res ^= sg(x);
}
if(res) cout << "Yes\n";
else cout << "No\n";
return 0;
}