博弈论-acwing893.集合-Nim游戏
补充知识
有向图游戏
给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿着有向边方向移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个结点,并且从每个局面向沿着合法行动能够到达下一个局面连有向边。
这个题每一堆石子都可以看做成一个有向图
Mex运算
设S表示一个非负整数集合。定义\(mex(S)\)为求不出属于集合S的最小非负整数的运算,即:\(mex(S) = min(x)\),x属于自然数,且x不属于S
SG函数
在有向图游戏中,对于每个节点x,设从x除法共有k条有向边,分别到达结点\(y_1,y_2,...,y_k\),定义\(SG(x)\)为x的后继结点\(y_1,y_2,...,y_k\)的\(SG()\)函数值构成的集合再执行\(mex(S)\)运算的结果,即:\(SG(x)=mex({SG(y_1),SG(y_2),...,SG(y_n)})\)。
特别的,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即:\(SG(G) = SG(s)\)
SG定理(有向图游戏的和)
设\(G_1,G_2,...,G_m\)是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏\(G_i\),并在\(G_i\)上行动一步。G被称为有向图游戏\(G_1,G_2,...,G_m\)的和。
有向图游戏的和SG函数在数值上等于它所包含的各个子游戏SG函数的异或和,即:\(SG(G) = SG(G_1)\bigoplus SG(G_2)\bigoplus ...\bigoplus SG(G_m)\)
存在结论:
对于n个图,如果\(SG(G_1)\bigoplus SG(G_2)\bigoplus ...\bigoplus SG(G_N) \neq 0\),则先手必胜,否则先手必败
acwing893.集合-Nim游戏
思路
\(h[i]\)表示的是每一堆石子有多少个,将每一个\(h[i]\)看做一张有向图
例如\(S=[2,5],h[1]=10\)
看h[1]这张有向图:
(ps:注意求SG函数需要mex()函数求,看其定义不要搞乱了)
#include<iostream>
#include<unordered_set>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100, M = 10010;
int n,m;
int f[M],s[N]; //f表示可能出现过的sg函数值,s表示可选正整数的集合
int sg(int x)
{
if(f[x] != -1) return f[x]; // 如果算出来过sg(x)就直接返回f[x]
unordered_set<int> S; // 用来存储该节点下一步的节点他们的sg函数值
for(int i = 0; i < n; i ++)
{
int sum = s[i];
if(x >= sum) S.insert(sg(x - sum)); // 如果存在下一步,将下一步节点sg函数值存入
}
// mex操作
for(int i = 0;;i ++)
{
if(!S.count(i)) return f[x] = i;
}
}
int main()
{
cin >> n ;
for(int i = 0; i < n; i ++) cin >> s[i];
cin >> m;
memset(f,-1,sizeof(f));
int res = 0;
for(int i = 0; i < m; i ++)
{
int x; // x表示这一堆石子的数量
cin >> x;
res ^= sg(x);
}
if(res) puts("Yes");
else puts("No");
return 0;
}