题解 P3235【[HNOI2014]江南乐】

Link

题意

一次操作可以将一堆数量为 iiF)的石子分为 jimodj 堆数量为 ij 的石子和 imodj 堆数量为 ij 的石子,其中 j 是每次操作时玩家自行选定,满足 2jiF 为给定。不能操作者输。

初始有 n 堆石子,第 i 堆石子的数量为 ai。多组数据,但 F 不改变。

T,n100F,ai105.

思路

我们定一个数的 SG 值为 f(x),一个局面的 SG 值为 f(state)=istatef(i)

于是求出每个数的 SG 值即可。

首先,对于 i<Ff(i)=0,因为显然此时无法取到石子,先手必败。

然后枚举 j,我们可以算出此时划分的局面的 SG 值。因为只有两种棋子的取值,且知道每堆的数量,所以算此时划分的局面的 SG 值是 O(1) 的。

v=max{ai},则此时时间复杂度为 O(v2+Tn),无法通过。

瓶颈在于计算 SG,我们观察式子,发现有 ij 这一形式,于是考虑整除分块。

但是式子里还有 imodj 这一形态,整除分块不好处理,怎么办呢?

考虑向奇偶性方向想,

  • 2ij 时,jimodj=j(ijij)=j(1+ij)i,发现此时 jimodj 的奇偶性不变,则此部分贡献不变;
  • 2|ij 时,imodj=ijij,则 imodj 奇偶性不变,此部分贡献不变。

发现无论 ij 的奇偶性,两种数量的石子的堆数中总有一种不变。

于是我们只需要枚举 2j 值就能代表所有情况了。

mex 可以暴力,因为集合里的数的个数为 O(v)×O(1)=O(v)

此时时间复杂度降为 O(vv+Tn),可以通过。

由于查询的 ai 值较少,不会涵盖整个值域,可以使用记忆化搜索,比递推快很多。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO{//by cyffff

}
const int N=1e5+10;
int n,T,f;
int SG[N];
inline int getSG(int i){
    if(i<f) return 0;
    if(~SG[i]) return SG[i];
    bool vis[100];
    memset(vis,0,sizeof(vis));
    for(int j=2,r;j<=i;j=r+1){
        r=i/(i/j);
        int res=0;
        bool flag=i%j;
        if(i%j&1) res^=getSG(i/j+1);
        if(j-i%j&1) res^=getSG(i/j);
        j++;
        vis[res]=i;
        if(flag){
            res=0;
            if(i%j&1) res^=getSG(i/j+1);
            if(j-i%j&1) res^=getSG(i/j);
            j++;
            vis[res]=i;
        }
    }
    for(int j=0;;j++)
        if(!vis[j])
            return SG[i]=j;
}
int main(){
    memset(SG,-1,sizeof(SG));
    T=read(),f=read();
    while(T--){
        n=read();
        int res=0;
        for(int i=1;i<=n;i++){
            res^=getSG(read());
        }
        write(res>0),putc(' ');
    } 
    flush();
}

再见 qwq~

posted @   ffffyc  阅读(2)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示