传送门
思路:
起初思路挺好,后来就比较奇怪的一道题目
玩法很像SDOI2016R2的硬币游戏,不过那个题目直接暴力SG就可以过,本题直接套规则只有30分;
稍微优化一下,不枚举k,直接每次xor一下后继状态的sg,可以得50分,复杂度
O(nlogn)
;
70分需要一个比较好玩的结论:
如果
⌊ni⌋=⌊nj⌋
,那么
sg(i)=sg(j)
我们可以归纳证明一下:
当
⌊ni⌋=1
时,显然
sg(i)=1
,因为它只能令自身反色
当
⌊ni⌋>1
时,
sg(i)
由
⌊n2i⌋,⌊n3i⌋
..的sg值决定,又
⌊nij⌋=⌊⌊ni⌋j⌋
,所以
⌊ni⌋
相同的位置后继状态相同,sg值也就相同了
然后就可以分块求了,
⌊ni⌋
的取值最多有
2n√
种,转移时考虑落在当前区间内的后继点奇偶就可以了,复杂度
O(n)
,可以得70分,个人以为这正是本题巧妙的地方
接下来的优化就比较玄学了,本人一开始并不知道怎么做,弃疗后百度了题解,好像是什么跑不满的小常数
O(n)
,还有什么玄学/hash,表示不是很懂,不过里面所说的合并sg给了我启发,因为有很多
⌊ni⌋
不相等的块sg值相同,这个优化比较奇怪但确实很好用,在
n=109
时用这个优化,块的数量只有9223个,所以我们只要求出一个块的sg后和前一个块比较,如果sg相同就合并,查找时二分一下就可以了
提示:本题中
sg
函数都不大
代码:
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
int n,m,q,cnt;
int pos[64005],f[64005];
struct node{
int l,r,sg;
}da[64005];
bool vis[1000];
void dp()
{
for (int i=pos[0];i;--i)
{
int t=0,mx=0,p=cnt;
for (int last=2,j=2;j<=n/pos[i];j=last+1)
{
for (;j*pos[i]>da[p].r&&p;--p);
if (p) last=da[p].r/pos[i];
vis[t^da[p].sg]=1;
mx=max(mx,t^da[p].sg);
if (last-j+1&1) t^=da[p].sg;
vis[t]=1,mx=max(mx,t);
}
for (int j=1;;++j)
if (!vis[j])
{
f[i]=da[++cnt].sg=j;
if (cnt>1&&da[cnt].sg==da[cnt-1].sg)
da[--cnt].l=pos[i-1]+1;
else
da[cnt].l=pos[i-1]+1,da[cnt].r=pos[i];
break;
}
for (int j=0;j<=mx;++j) vis[j]=0;
}
}
main()
{
scanf("%d%d",&n,&q);
for (int last,i=1;i<=n;i=last+1) pos[++pos[0]]=last=n/(n/i);
dp();
for (int x,ans;q;--q)
{
ans=0;
scanf("%d",&m);
for (;m;--m)
scanf("%d",&x),
ans^=f[lower_bound(pos+1,pos+pos[0]+1,n/(n/x))-pos];
puts(ans?"Yes":"No");
}
}