bzoj4035 [HAOI2015]数组游戏(博弈论sg函数+分块)

出题人kzf告诉我们,可以修改一下游戏的定义:每一步可以选择黑格子或白格子,最终能将所有格子都翻成黑色的人赢。由于每次翻转黑格子的操作都不可能直接带来胜利,而任何一步选取黑格子进行的有利操作都能由对方通过再翻一次这个格子抵消掉,根据假设“两个人都是足够聪明的”,我们知道游戏中一定不会有人选择黑格子。这就保证了这种转换的正确性。
因此我们可以把每个黑色的位置都看成偶数个白格子叠加起来得到的,并将每个位置当做一个独立的游戏来计算SG函数,就可以套用常规的博弈题思路了。根据游戏规则,我们有

SG(i)=mex1kN/i{2tk{SG(ti)}} S G ( i ) = m e x 1 ≤ k ≤ N / i ⁡ { ⊕ 2 ≤ t ≤ k ⁡ { S G ( t ∗ i ) } }

( 表示异或和,mex表示求集合中未出现过的最小自然数的操作)

考虑到当一个点i走到N要跳的步数一定时,我们可以把这些i的倍数的序列映射到另一个序列2…N/i,这样我们就可以用数学归纳证明每个点i的SG值只与从它跳到N需要的次数(即 N / i)有关。然后我们可以注意到,i从1到N中,N / i只会有 O(N) O ( N ) 种取值,所以我们只需递推 O(N) O ( N ) 轮就可以了。

然后存的时候用类似杜教筛的解决办法,如果x>sqrt(n)了,那么就存在b[n/x]中,可以证明这样是不会重复的。
然后复杂度的话,好难算qaq,应该是小常数的 O(n) O ( n ) 左右,反正跑得飞起。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 100010
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(T==S){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,nn,m,a[N],b[N],q[N],c[N];
bool mk[N];
inline void init(){
    int tot=0;
    for(int i=1,lst;i<=n;i=lst+1){
        lst=n/(n/i);q[++tot]=n/i;
    }for(int ii=tot;ii>=1;--ii){
        int x=q[ii],tmp=0,cnt=0;//n/i==x 的sg[i] if(x<nn) 存在a[x] else 存在b[n/x]
        for(int j=2,lst;j<=x;j=lst+1){
            lst=x/(x/j);int y=x/j,z=y>nn?b[n/y]:a[y];
            c[++cnt]=tmp^z;mk[c[cnt]]=1;
            if((lst-j+1)&1) tmp^=z;
        }tmp=1;while(mk[tmp]) ++tmp;
        x>nn?b[n/x]=tmp:a[x]=tmp;
        for(int i=1;i<=cnt;++i) mk[c[i]]=0;
    }
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();nn=sqrt(n);init();
    while(m--){
        int owo=read(),res=0;
        while(owo--){
            int x=read();x=n/x;
            res^=(x>nn?b[n/x]:a[x]);
        }puts(res?"Yes":"No");
    }return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值