博弈论神题。。
首先把问题转化为存在若干白点,然后将所有点翻转成黑点,先全部翻转的为胜。这和原题是等价的,因为如果某一步选一个黑点翻是必胜策略,那么下一步对手可以选择翻同一个点而使局面变回来,因此考虑双方都是采取最好的策略因此不会有一方翻黑点。
那么就把每一个白点看成是一个子游戏,最后将SG函数全部异或起来即可,由SG定理可知有:
SG(i)=mex{SG[i*1]^SG[i*2]^...^SG[i*k]},k=[2,N/i]。
然而N为10^9,因此不能直接递推。注意到实际上某一个SG[i]函数的值只和N/i有关,因此有用的状态只有O(N^0.5)个,然后根据上面的式子递推即可。
但是还要考虑有用状态的保存问题,注意到有N^0.5个状态的下标<=N^0.5,这部分直接保存即可;另有N^0.5个状态的下标>=N^0.5,但是N/下标<=N^0.5且各不相同,因此用N除之后再保存即可。
时间复杂度O(N),不过常数较小,可以通过。
AC代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 100005
using namespace std;
int n,m,c[2][N],a[N]; bool bo[N];
int nxt(int x,int y){ return (x==y)?y+1:y/(y/(x+1)); }
void pfs(){
int i,j,now,x,t,cnt;
for (i=1; i<=n; i=nxt(i,n)){
now=cnt=0;
for (j=2; j<=i; j=nxt(j,i)){
x=i/j; t=(x>m)?c[1][n/x]:c[0][x];
a[++cnt]=now^t; bo[a[cnt]]=1;
if ((i/x-i/(x+1))&1) now^=t;
}
now=1; while (bo[now]) now++;
if (i>m) c[1][n/i]=now; else c[0][i]=now;
for (j=1; j<=cnt; j++) bo[a[j]]=0;
}
}
int main(){
scanf("%d",&n); m=(int)sqrt(n); pfs();
int cas,cnt; scanf("%d",&cas);
while (cas--){
scanf("%d",&cnt); int ans=0,x;
while (cnt--){
scanf("%d",&x); x=n/x;
ans^=(x>m)?c[1][n/x]:c[0][x];
}
puts((ans)?"Yes":"No");
}
}
by lych
2016.3.15