=== ===
这里放传送门
=== ===
题解
这题对于SG函数的递推非常好想,因为对于有x个石子的一堆来说,选定一个m以后分成的m堆一定要么是 ⌊xm⌋ 个,要么是 ⌊xm⌋+1 个,因为把除以m以后的那些余数一堆一个就能满足要求了。因为后继状态是每个石子堆的异或值,所以只要算一下两种石子堆的奇偶性然后用记忆化搜索得到后继SG值就可以了。
但是如果每次都暴力枚举m的话时间复杂度是 O(n2) 级别的。考虑优化,因为它有一个下取整除法的操作,可以考虑利用分块枚举除法的做法。设当前数字为x,除以m的商为u,余数为r,那么值为 ⌊xm⌋+1 的就有r堆,剩下的m-r堆都是 ⌊xm⌋ 的。列几个数就可以发现,如果当前这一段区间长度大于2,那么随着除数m每次加一,余数的变化是每次减少u直到减少到0的。
而因为要考察r和m-r的奇偶性,所以可以对u分奇偶讨论。当u是奇数的时候,r的奇偶性每次都会改变,但m因为是每次加一,所以它的奇偶性也会每次改变,也就是说m-r的奇偶性是不变的,那么 ⌊xm⌋ 的那些堆到底有没有影响就是确定的了,直接计算就可以。这里的“有影响”是说它有奇数堆,不会在异或操作中被消掉。
而因为区间长度是大于2的,所以状态一定会改变至少一次,也就是说 ⌊xm⌋+1 的那些堆有影响和无影响的状态肯定都存在于这一块里面,加进去就可以了。同样,当u是偶数的时候,r的奇偶性是不变的,也就是 ⌊xm⌋+1 的那些堆状态是确定的,再把 ⌊xm⌋ 的那些堆有影响和无影响的状态都加进去就可以了。时间复杂度是 O(nn√) 的。这里用记忆化搜索比预处理要快很多,因为实际上用到的状态很少。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int T,F,n,sum,sg[100010];
int get_sg(int u){
int res,k,tail,now;
if (sg[u]!=-1) return sg[u];
bool ext[10010];
memset(ext,false,sizeof(ext));
for (int i=2;i<=u;i=tail+1){
tail=min(u/(u/i),u);
now=0;
res=u/i;k=u%i;
if (tail-i+1<2){
if (k&1) now^=get_sg(res+1);
if ((i-k)&1) now^=get_sg(res);
ext[now]=true;
}else
if (res&1){
if ((i-k)&1) now^=get_sg(res);
ext[now]=true;
now^=get_sg(res+1);
ext[now]=true;
}else{
if (k&1) now^=get_sg(res+1);
ext[now]=true;
now^=get_sg(res);
ext[now]=true;
}
}
for (int i=0;;i++)
if (ext[i]==false){
sg[u]=i;break;
}
return sg[u];
}
int main()
{
memset(sg,-1,sizeof(sg));
scanf("%d%d",&T,&F);
for (int i=0;i<F;i++) sg[i]=0;
for (int wer=1;wer<=T;wer++){
scanf("%d",&n);sum=0;
for (int i=1;i<=n;i++){
int x;scanf("%d",&x);
sum^=get_sg(x);
}
if (sum==0) printf("0");
else printf("1");
if (wer==T) printf("\n");
else printf(" ");
}
return 0;
}