出题人kzf告诉我们,可以修改一下游戏的定义:每一步可以选择黑格子或白格子,最终能将所有格子都翻成黑色的人赢。由于每次翻转黑格子的操作都不可能直接带来胜利,而任何一步选取黑格子进行的有利操作都能由对方通过再翻一次这个格子抵消掉,根据假设“两个人都是足够聪明的”,我们知道游戏中一定不会有人选择黑格子。这就保证了这种转换的正确性。
因此我们可以把每个黑色的位置都看成偶数个白格子叠加起来得到的,并将每个位置当做一个独立的游戏来计算SG函数,就可以套用常规的博弈题思路了。根据游戏规则,我们有
( ⊕ ⊕ 表示异或和,mex表示求集合中未出现过的最小自然数的操作)
考虑到当一个点i走到N要跳的步数一定时,我们可以把这些i的倍数的序列映射到另一个序列2…N/i,这样我们就可以用数学归纳证明每个点i的SG值只与从它跳到N需要的次数(即 N / i)有关。然后我们可以注意到,i从1到N中,N / i只会有 O(N−−√) O ( N ) 种取值,所以我们只需递推 O(N−−√) O ( N ) 轮就可以了。
然后存的时候用类似杜教筛的解决办法,如果x>sqrt(n)了,那么就存在b[n/x]中,可以证明这样是不会重复的。
然后复杂度的话,好难算qaq,应该是小常数的
O(n)
O
(
n
)
左右,反正跑得飞起。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 100010
inline char gc(){
static char buf[1<<16],*S,*T;
if(T==S){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
return *S++;
}
inline int read(){
int x=0,f=1;char ch=gc();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
return x*f;
}
int n,nn,m,a[N],b[N],q[N],c[N];
bool mk[N];
inline void init(){
int tot=0;
for(int i=1,lst;i<=n;i=lst+1){
lst=n/(n/i);q[++tot]=n/i;
}for(int ii=tot;ii>=1;--ii){
int x=q[ii],tmp=0,cnt=0;//n/i==x 的sg[i] if(x<nn) 存在a[x] else 存在b[n/x]
for(int j=2,lst;j<=x;j=lst+1){
lst=x/(x/j);int y=x/j,z=y>nn?b[n/y]:a[y];
c[++cnt]=tmp^z;mk[c[cnt]]=1;
if((lst-j+1)&1) tmp^=z;
}tmp=1;while(mk[tmp]) ++tmp;
x>nn?b[n/x]=tmp:a[x]=tmp;
for(int i=1;i<=cnt;++i) mk[c[i]]=0;
}
}
int main(){
// freopen("a.in","r",stdin);
n=read();m=read();nn=sqrt(n);init();
while(m--){
int owo=read(),res=0;
while(owo--){
int x=read();x=n/x;
res^=(x>nn?b[n/x]:a[x]);
}puts(res?"Yes":"No");
}return 0;
}