题目传送门
题目分析:
每堆石子是独立的,可以求出每堆的SG函数再异或起来。
所以考虑预处理(或者记忆化搜索)SG函数。
设石子数量为
n
n
n,枚举它分成的堆数
i
i
i,那么较小堆的石子数是
⌊
n
i
⌋
\lfloor\frac ni\rfloor
⌊in⌋(较大堆是
⌊
n
i
⌋
\lfloor\frac ni\rfloor
⌊in⌋+1),设
n
/
i
n/i
n/i的余数是
k
k
k,那么大堆的数量是
k
k
k,小堆的数量是
i
−
k
i-k
i−k,根据
k
k
k和
i
−
k
i-k
i−k的奇偶性进行异或得到分成
i
i
i堆的SG值,在vis数组中标记一下就行了。
但是这样做是 O ( n 2 ) O(n^2) O(n2)的,注意到 ⌊ n i ⌋ \lfloor\frac ni\rfloor ⌊in⌋只有 n \sqrt n n个取值(画一个反比例函数根据对称性就看得出来),考虑整除分块优化。
若
⌊
n
i
⌋
=
⌊
n
i
+
1
⌋
\lfloor\frac ni\rfloor=\lfloor\frac n{i+1}\rfloor
⌊in⌋=⌊i+1n⌋,大小堆的石子数是一样的,那么多出来的一堆肯定是从
k
k
k个大堆里面选
⌊
n
i
⌋
\lfloor\frac ni\rfloor
⌊in⌋堆,每堆取出一个石子放到一起形成一个新堆,对应的
k
−
=
⌊
n
i
⌋
,
小
堆
+
=
⌊
n
i
⌋
+
1
k-=\lfloor\frac ni\rfloor,小堆+=\lfloor\frac ni\rfloor+1
k−=⌊in⌋,小堆+=⌊in⌋+1,由于是根据奇偶性进行异或并标记vis数组,那么
⌊
n
i
⌋
\lfloor\frac ni\rfloor
⌊in⌋相等的一段最多只会有i和i+1两种不同的贡献,i+2的大小堆堆数的奇偶性与i相同,所以一段只需要算两次就可以了。
复杂度
O
(
n
n
)
O(n\sqrt n)
O(nn)
Code:
#include<cstdio>
#include<cctype>
#define maxn 100005
inline void read(int &a){
char c;while(!isdigit(c=getchar()));
for(a=c-'0';isdigit(c=getchar());a=a*10+c-'0');
}
int SG[maxn],F,T,n,vis[maxn];
int calc(int k){
int x,y,s;
for(int i=2,j;i<=k;i=j+1){
s=k/i;
y=k%i,x=i-y;
vis[((x&1)*SG[s])^((y&1)*SG[s+1])]=k;
j=k/s;
if(i<j) y=k%(i+1),x=i+1-y,vis[((x&1)*SG[s])^((y&1)*SG[s+1])]=k;
}
x=0;while(vis[x]==k) x++;
return x;
}
void Pre(){for(int i=F;i<=100000;i++) SG[i]=calc(i);}
int main()
{
read(T),read(F);
Pre();
while(T--){
read(n);int x=0,y;
while(n--) read(y),x^=SG[y];
printf("%d%c",x?1:0,T?32:10);
}
}