bzoj4035 T3 SG函数

       博弈论神题。。

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

       那么就把每一个白点看成是一个子游戏,最后将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

你可能感兴趣的:(博弈论,递推,sg函数,SG定理)