题意
有
n
n
n堆石子,给定
F
F
F,每次操作可以把一堆石子数不小于
F
F
F的石子平均分配成若干堆(堆数
>
1
>1
>1).
平均分配即指分出来的石子数中最大值减最小值不超过
1
1
1.不能进行操作就算输.询问先手是否有必胜策略.
分析
因为每一推石子实际上是独立的.于是就可以求出每一堆石子的 S G SG SG函数后再异或起来.
于是看看怎么求 S G ( N ) SG(N) SG(N).方法是枚举分成的堆数 i i i.因为最大 − - −最小 < = 1 <=1 <=1,那么只可能有两种取值.设:
- 分出来的较小值为
r
r
r, 较大值就是
r
+
1
r+1
r+1
分出来的较大值的堆数为 k k k, 较小值的堆数就是 i − k i-k i−k
那么有 r = N / i r=N/i r=N/i, k = N % i k=N\%i k=N%i. 那么只要把 S G ( r ) SG(r) SG(r)异或 ( i − k ) (i-k) (i−k)次,把 S G ( r + 1 ) SG(r+1) SG(r+1)异或 k k k次.就能得到一个后继状态的 S G 值 SG值 SG值.所以把所有的算出来的值取 m e x mex mex就得到了 S G ( N ) SG(N) SG(N).这里 m e x mex mex指没有出现过的最小非负整数值.因为异或偶数次相当于没有异或,所以判断一下 k k k或者 ( i − k ) (i-k) (i−k)是否为奇数然后异或一次就行了.
但是这样的时间复杂度是 O ( N 2 ) O(N^2) O(N2)的,所以我们需要优化.
优化方法类似于莫比乌斯反演中所用到的整除分块.我们对于
r
r
r值相等的
i
i
i一起处理.假设我们在处理一个区间
[
i
,
j
]
[i,j]
[i,j],满足
r
i
=
r
i
+
1
=
.
.
.
=
r
j
=
r
r_i=r_{i+1}=...=r_j=r
ri=ri+1=...=rj=r.那么从
k
i
k_i
ki到
k
j
k_j
kj一定是每一次减少
r
r
r,因为相当于在
k
k
k个较大值中选出
r
r
r个,每一个取走
1
1
1,组成一堆新的石子.
这个时候我们可以发现
k
i
k_i
ki的奇偶性一定与
k
i
+
2
k_{i+2}
ki+2相同,且
(
i
−
k
i
)
(i-k_i)
(i−ki)的奇偶性也与
(
i
+
2
−
k
i
+
2
)
(i+2-k_{i+2})
(i+2−ki+2)相同,所以说分成
i
i
i堆和分成
i
+
2
i+2
i+2推石子得到的
S
G
SG
SG值是一样的.那么
[
i
+
2
,
j
]
[i+2,j]
[i+2,j]的值都对
m
e
x
mex
mex没有贡献,所以我们只需要算
i
i
i和
i
+
1
i+1
i+1的值就行了.那么时间复杂度就被降到了
O
(
n
n
)
O(n\sqrt n)
O(nn).
CODE
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
namespace READ {
inline void read(int &num) {
char ch; while((ch=getchar())<'0'||ch>'9');
for(num=0;ch>='0'&&ch<='9';num=num*10+ch-'0',ch=getchar());
}
}
using namespace READ;
const int MAXN = 100005;
int T, F, SG[MAXN], vis[MAXN];
inline int sg(int N) { //记忆化搜索,预处理是卡着时限过的,而记忆化搜索会快很多
if(~SG[N]) return SG[N];
if(N < F) return SG[N]=0;
int now, r, k;
for(int i = 2, j; i <= N; i = j+1) { //先把要用到的sg求出来,否则vis数组可能会被重复标号出错
j = N/(N/i); //(但貌似这道题不会WA)
r = N/i, k = N%i;
if(k&1) sg(r+1);
if((i-k)&1) sg(r);
if(j > i) {
r = N/(i+1), k = N%(i+1);
if(k&1) sg(r+1);
if(((i+1)-k)&1) sg(r);
}
}
for(int i = 2, j; i <= N; i = j+1) {
j = N/(N/i);
r = N/i, k = N%i;
now = 0;
if(k&1) now ^= SG[r+1];
if((i-k)&1) now ^= SG[r];
vis[now] = N;
if(j > i) { //可能区间长度只有1
r = N/(i+1), k = N%(i+1);
now = 0;
if(k&1) now ^= SG[r+1];
if(((i+1)-k)&1) now ^= SG[r];
vis[now] = N;
}
}
int mex = 0;
for(; vis[mex] == N; ++mex);
return SG[N]=mex;
}
int main () {
memset(SG, -1, sizeof SG);
read(T), read(F);
while(T--) {
int ans = 0, x, n; read(n);
while(n--)
read(x), ans ^= sg(x);
putchar(ans ? '1' : '0');
if(T) putchar(' '); //注意格式
}
}