[BZOJ3576][Hnoi2014]江南乐(博弈)

本文介绍了一种针对SG函数的高效求解方法,通过分析不同堆石子数量的组合特性,采用记忆化搜索实现时间复杂度的优化,达到O(n√n)级别。文章详细解释了如何利用分块枚举除法来减少不必要的计算,并给出了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

=== ===

这里放传送门

=== ===

题解

这题对于SG函数的递推非常好想,因为对于有x个石子的一堆来说,选定一个m以后分成的m堆一定要么是 xm 个,要么是 xm+1 个,因为把除以m以后的那些余数一堆一个就能满足要求了。因为后继状态是每个石子堆的异或值,所以只要算一下两种石子堆的奇偶性然后用记忆化搜索得到后继SG值就可以了。

但是如果每次都暴力枚举m的话时间复杂度是 O(n2) 级别的。考虑优化,因为它有一个下取整除法的操作,可以考虑利用分块枚举除法的做法。设当前数字为x,除以m的商为u,余数为r,那么值为 xm+1 的就有r堆,剩下的m-r堆都是 xm 的。列几个数就可以发现,如果当前这一段区间长度大于2,那么随着除数m每次加一,余数的变化是每次减少u直到减少到0的。

而因为要考察r和m-r的奇偶性,所以可以对u分奇偶讨论。当u是奇数的时候,r的奇偶性每次都会改变,但m因为是每次加一,所以它的奇偶性也会每次改变,也就是说m-r的奇偶性是不变的,那么 xm 的那些堆到底有没有影响就是确定的了,直接计算就可以。这里的“有影响”是说它有奇数堆,不会在异或操作中被消掉。

而因为区间长度是大于2的,所以状态一定会改变至少一次,也就是说 xm+1 的那些堆有影响和无影响的状态肯定都存在于这一块里面,加进去就可以了。同样,当u是偶数的时候,r的奇偶性是不变的,也就是 xm+1 的那些堆状态是确定的,再把 xm 的那些堆有影响和无影响的状态都加进去就可以了。时间复杂度是 O(nn) 的。这里用记忆化搜索比预处理要快很多,因为实际上用到的状态很少。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int T,F,n,sum,sg[100010];
int get_sg(int u){
    int res,k,tail,now;
    if (sg[u]!=-1) return sg[u];
    bool ext[10010];
    memset(ext,false,sizeof(ext));
    for (int i=2;i<=u;i=tail+1){
        tail=min(u/(u/i),u);
        now=0;
        res=u/i;k=u%i;
        if (tail-i+1<2){
            if (k&1) now^=get_sg(res+1);
            if ((i-k)&1) now^=get_sg(res);
            ext[now]=true;
        }else
          if (res&1){
              if ((i-k)&1) now^=get_sg(res);
              ext[now]=true;
              now^=get_sg(res+1);
              ext[now]=true;
          }else{
              if (k&1) now^=get_sg(res+1);
              ext[now]=true;
              now^=get_sg(res);
              ext[now]=true;
          }
    }
    for (int i=0;;i++)
      if (ext[i]==false){
          sg[u]=i;break;
      }
    return sg[u];
}
int main()
{
    memset(sg,-1,sizeof(sg));
    scanf("%d%d",&T,&F);
    for (int i=0;i<F;i++) sg[i]=0;
    for (int wer=1;wer<=T;wer++){
        scanf("%d",&n);sum=0;
        for (int i=1;i<=n;i++){
            int x;scanf("%d",&x);
            sum^=get_sg(x);
        }
        if (sum==0) printf("0");
        else printf("1");
        if (wer==T) printf("\n");
        else printf(" ");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值