题目大意:
有一个长度为N的数组,甲乙两人在上面进行这样一个游戏:首先,数组上有一些格子是白的,有一些是黑的。然
后两人轮流进行操作。每次操作选择一个白色的格子,假设它的下标为x。接着,选择一个大小在1~n/x之间的整数
k,然后将下标为x、2x、…、kx的格子都进行颜色翻转。不能操作的人输。现在甲(先手)有一些询问。每次他
会给你一个数组的初始状态,你要求出对于这种初始状态他是否有必胜策略。
思路:
发现这个题目的每个格子里面只有黑白两种状态,其实和HNOI的一道叫做分裂游戏的题目比较像,但是那个题目每个格子里面是有多个石子来取的,所以我们可以把这题的条件看成白格子的地方有一个石子,黑格子的地方无东西,我们的目的就是要把所有的石头取走。
每一个有白格子的地方单独看做是一个游戏,后继的状态即后面的若干个格子进行颜色翻转,也就是加上一个石头,SG函数转移即可。
其实也可以换一个角度去想,就是后面的格子虽然翻转了,但是我们最后要让所有的游戏求和,所以目前翻转了的不是选中的格子最后总是要翻回来的,要不然求和就和不正确,这个想法和上面的做法也是同样的。
所以可以得到转移的方程
SG[i]=mex(SG[i∗1]xorSG[i∗2]...xor...SG[k])k∈[2,ni]
S
G
[
i
]
=
m
e
x
(
S
G
[
i
∗
1
]
x
o
r
S
G
[
i
∗
2
]
.
.
.
x
o
r
.
.
.
S
G
[
k
]
)
k
∈
[
2
,
n
i
]
但是暴力转移显然是不行的。
于是把表打出来我们发现当
⌊ni⌋=⌊nj⌋
⌊
n
i
⌋
=
⌊
n
j
⌋
时,即在同一个整除块内的时候SG函数的值是相等的。为什么呢?
用数学归纳法来证明:
i>n2
i
>
n
2
显然成立,这个时候都是1。
否则,因为
⌊ni⌋=⌊nj⌋
⌊
n
i
⌋
=
⌊
n
j
⌋
,所以有
⌊ni∗k⌋=⌊nj∗k⌋
⌊
n
i
∗
k
⌋
=
⌊
n
j
∗
k
⌋
以及
⌊ni⌋k=⌊nj⌋k
⌊
n
i
⌋
k
=
⌊
n
j
⌋
k
,所有它们后面的状态都是在同一个块内的,所以相等。
然后我们就可以一维用分块枚举
i
i
,然后在的范围内再用一次分块枚举
k
k
就好了。
然后到了这里我就遇到了一个问题:无法表示SG函数的状态,然后就用了一波map,果然T了。
其实发现在的时候,每一个块的端点都连续,可以用一个数组,当
i>n−−√
i
>
n
时,整除出来的结果是连续的,又可以用一个数组。
/*==========================
* Author : ylsoi
* Problem : bzoj4035
* Algodithm : SG
* Time : 2018.6.3
* ========================*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<climits>
#include<ctime>
using namespace std;
void File(){
freopen("bzoj4035.in","r",stdin);
freopen("bzoj4035.out","w",stdout);
}
template<typename T>bool chkmax(T &_,T __){return _<__ ? (_=__,1) : 0;}
template<typename T>bool chkmin(T &_,T __){return _>__ ? (_=__,1) : 0;}
#define REP(i,a,b) for(register int i=a;i<=b;++i)
#define DREP(i,a,b) for(register int i=a;i>=b;--i)
#define MREP(i,x) for(register int i=beg[x];i;i=E[i].last)
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
#define inf INT_MAX
const int maxn=1e9+10;
const int maxm=100+10;
int n,q,m,a[maxm],SG[maxm*maxm],L[maxn/10000],R[maxn/10000],cnt;
int va1[maxn/10000],va2[maxn/10000],block;
bool in[maxn/10000];
int get(int x){return x<=block ? va1[x] : va2[n/x];}
void into(int x,int va){
if(x<=block)va1[x]=va;
else va2[n/x]=va;
}
void init(){
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
++cnt;
L[cnt]=l;
R[cnt]=r;
}
DREP(i,cnt,1){
mem(in);
int b=n/L[i],sum=0;
for(int l=1,r;l<=b;l=r+1){
int c=b/l,tmp=get(L[i]*l);
r=b/c;
in[sum]=1;
in[sum^tmp]=1;
if((r-l+1)%2)sum^=tmp;
}
REP(j,0,maxn)if(!in[j]){
into(L[i],j);
break;
}
}
}
int main(){
File();
scanf("%d%d",&n,&q);
block=sqrt(n);
init();
REP(i,1,q){
int ans=0;
scanf("%d",&m);
REP(j,1,m){
int u;
scanf("%d",&u);
ans^=get(u);
}
if(ans)puts("Yes");
else puts("No");
}
return 0;
}