大致题意: 一个(1sim n)的棋盘,每组询问给出棋盘上白色格子的下标。对弈双方轮流操作,每次可以选择一个白色格子(设其下标为(x)),将下标为(x,2x,...,kx)的格子颜色翻转,其中(1le klelfloorfrac nx floor)。问先手是否有必胜策略。
(SG)函数
考虑我们求出每个格子的(SG)函数,则一个棋盘的(SG)值就是所有白色格子(SG)函数的异或和。
众所周知一个状态的(SG)函数等于其后继状态(SG)值的(mex)。
考虑一个节点(x)的后继状态,就会发现:
[SG(x)=mex{0,SG(2x),SG(2x)oplus SG(3x),SG(2x)oplus SG(3x)oplus SG(4x)...}
]
由于(nle10^9),直接暴力求出所有(SG(x))是不现实的,需要考虑优化。
除法分块
根据此题中(SG)函数的转移式,容易发现,若(lfloorfrac nx floor=lfloorfrac ny floor),则(SG(x)=SG(y))。
也就是说,可以通过除法分块,将(SG)函数分成(O(sqrt n))段。
而要求出某一段的(SG)函数,设这段开头为(p_i),就需要在(lfloorfrac n{p_i} floor)范围内再次除法分块,利用每一段的(SG)值与前缀异或和更新答案。
注意除法分块过程中维护前缀异或和时,要根据这一段的奇偶性判断是否给前缀异或和异或上这一段的(SG)值。
具体实现详见代码,还是非常简单的。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define SN 32000
#define SG(x) ((x)<=sn?S[0][x]:S[1][n/(x)])//把SG函数分两个数组存储
using namespace std;
int n,m,p[2*SN+5],S[2][SN+5],vis[2*SN+5];
int main()
{
RI l,r;for(scanf("%d",&n),l=1;l<=n;l=r+1) p[++m]=l,r=n/(n/l);//除法分块,求出每一段
RI i,t,s,sn=sqrt(n);for(i=m;i;--i)//倒枚每一段求出SG函数
{
for(t=n/p[i],s=0,l=2;l<=t;l=r+1) r=t/(t/l),//再次除法分块
vis[s^SG(l*p[i])]=i,(r-l+1)&1&&(s^=SG(l*p[i]));//vis记录每种值否出现过,如果这段长度为奇数则更新前缀异或和
SG(p[i])=1;W(vis[SG(p[i])]==i) ++SG(p[i]);//求mex,由于后继状态必然存在0,因此SG初始化为1
}
RI Qt,x;scanf("%d",&Qt);W(Qt--)//处理询问
{scanf("%d",&t),s=0;W(t--) scanf("%d",&x),s^=SG(x);puts(s?"Yes":"No");}//把SG值异或起来判断是否为0
return 0;
}