BZOJ 3576: [Hnoi2014]江南乐【SG函数】

题目传送门

题目分析:

每堆石子是独立的,可以求出每堆的SG函数再异或起来。
所以考虑预处理(或者记忆化搜索)SG函数。
设石子数量为 n n n,枚举它分成的堆数 i i i,那么较小堆的石子数是 ⌊ n i ⌋ \lfloor\frac ni\rfloor in(较大堆是 ⌊ n i ⌋ \lfloor\frac ni\rfloor in+1),设 n / i n/i n/i的余数是 k k k,那么大堆的数量是 k k k,小堆的数量是 i − k i-k ik,根据 k k k i − k i-k ik的奇偶性进行异或得到分成 i i i堆的SG值,在vis数组中标记一下就行了。

但是这样做是 O ( n 2 ) O(n^2) O(n2)的,注意到 ⌊ n i ⌋ \lfloor\frac ni\rfloor in只有 n \sqrt n n 个取值(画一个反比例函数根据对称性就看得出来),考虑整除分块优化。

⌊ n i ⌋ = ⌊ n i + 1 ⌋ \lfloor\frac ni\rfloor=\lfloor\frac n{i+1}\rfloor in=i+1n,大小堆的石子数是一样的,那么多出来的一堆肯定是从 k k k个大堆里面选 ⌊ n i ⌋ \lfloor\frac ni\rfloor in堆,每堆取出一个石子放到一起形成一个新堆,对应的 k − = ⌊ n i ⌋ , 小 堆 + = ⌊ n i ⌋ + 1 k-=\lfloor\frac ni\rfloor,小堆+=\lfloor\frac ni\rfloor+1 k=in+=in+1,由于是根据奇偶性进行异或并标记vis数组,那么 ⌊ n i ⌋ \lfloor\frac ni\rfloor in相等的一段最多只会有i和i+1两种不同的贡献,i+2的大小堆堆数的奇偶性与i相同,所以一段只需要算两次就可以了。
复杂度 O ( n n ) O(n\sqrt n) O(nn )

Code:

#include<cstdio>
#include<cctype>
#define maxn 100005
inline void read(int &a){
    char c;while(!isdigit(c=getchar()));
    for(a=c-'0';isdigit(c=getchar());a=a*10+c-'0');
}
int SG[maxn],F,T,n,vis[maxn];
int calc(int k){
    int x,y,s;
    for(int i=2,j;i<=k;i=j+1){
        s=k/i;
        y=k%i,x=i-y;
        vis[((x&1)*SG[s])^((y&1)*SG[s+1])]=k;
        j=k/s;
        if(i<j) y=k%(i+1),x=i+1-y,vis[((x&1)*SG[s])^((y&1)*SG[s+1])]=k;
    }
    x=0;while(vis[x]==k) x++;
    return x;
}
void Pre(){for(int i=F;i<=100000;i++) SG[i]=calc(i);}
int main()
{
    read(T),read(F);
    Pre();
    while(T--){
        read(n);int x=0,y;
        while(n--) read(y),x^=SG[y];
        printf("%d%c",x?1:0,T?32:10);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值