您的位置:首页 > 其它

【BZOJ4035】数组游戏,博弈论+分块乱搞

2017-03-14 09:22 417 查看
传送门

思路:

起初思路挺好,后来就比较奇怪的一道题目

玩法很像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");
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: