博弈论神题。。
首先把问题转化为存在若干白点,然后将所有点翻转成黑点,先全部翻转的为胜。这和原题是等价的,因为如果某一步选一个黑点翻是必胜策略,那么下一步对手可以选择翻同一个点而使局面变回来,因此考虑双方都是采取最好的策略因此不会有一方翻黑点。
那么就把每一个白点看成是一个子游戏,最后将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