SG函数
SG 函数
先定义,SG
函数对应有向无环图(DAG
)上的一种游戏:有一枚棋子在起点上,每次可以沿着边往后移动,谁无法移动谁就输了。
公平组合游戏可以转换成他,只需要将局面中的所有状态看成一个节点,合法行动看成有向边。
判断必胜需要求解的就是起点的 SG
。
对于终点(没有出边),
对于其他点
然后对于起点,若
证明:
- 全
局面 先手必败。 - 对于当前局面若
,那么说明可以到达 ,走到 的点上,那么抛给对手就是 。 - 若
,说明无法到达 ,那么只能给对手 。
若先手局面为
反之,若为0,那么他只能给对手非0,对手给他0,最后他肯定得到全0,必败,后手必胜。
那有的同学肯定好奇:SG为什么不直接取0/1?
那是因为可能会出现多张有向图的情况,此时的结论同 nim
:将各SG异或,如果为0,先手必败,否则必胜。
设总值为
证明分三步,类似于 nim
:
-
全
局面 先手必败。 -
对于当前局面若
,存在某种方式将其变为 :考虑
的最高的为 二进制位(记为 ),原序列中肯定有至少一个(奇数个)图的SG第 位为 。(设为 )所以 (前面的位保持不变,然后第 位变为 ,后面就算均增加也会减少)。那么由于 时可以走到 ,所以,让这个有向图走到 的点上。那么新的异或值就是 ,抛给对手一个 的局面。 -
对于当前局面若
,不存在任何方式将其变为 :用反证法,假设存在某种方式,且走动的记为
,那么有 ,且 ,两个式子异或,其余的数都消去,剩下 ,即 ,而mex
定义为没有出现过的最小者,只能走到 ,矛盾,所以不存在。
而当先手的局面
反之
所以,我们只需要计算异或和,判断是否等于零即可。
对于本题,只需要分堆考虑,对于每一堆,只需要将石子个数当作状态,然后考虑集合中可以取的(比如
#include<cstdio>
#include<vector>
using namespace std;
const int N=110;
bool st[N];
int k,s[N],n,x,res,f[N*N];
int sg(int x){
if(f[x])return f[x];
vector<int>g;
for(int i=1;i<=k;++i)
if(x>=s[i])g.push_back(sg(x-s[i]));
for(int v:g)st[v]=1;
for(int i=0;;++i)
if(!st[i]){
for(int v:g)st[v]=0;
return f[x]=i;
}
}
int main(){
scanf("%d",&k);
for(int i=1;i<=k;++i)scanf("%d",s+i);
scanf("%d",&n);
for(int i=1;i<=n;++i){
int x;
scanf("%d",&x);
res^=sg(x);
}
if(res)puts("Yes");
else puts("No");
return 0;
}
本文作者:wscqwq
本文链接:https://www.cnblogs.com/wscqwq/p/17651966.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步