「学习笔记」min25 筛

貌似看懂板子了,那么来更一更

min25筛

求积性函数前缀和,复杂度 O(n0.75logn),常数比较小

不支持多点求,同时需要满足 f(prime) 是关于 prime 的低次多项式(很低次)

这个低次比如 φ(prime)=prime1,其实这个东西是个能快速求的多项式就行了

先把 f(i) 转成质数的和加合数的和,那么这个算法主要是分成两个部分:第一个 DP 和第二个 DP

Part I:DP

考虑如下一个 DP:

gk(n,j)=i=1n[i{prime}||minp(i)>Pj]ik

表示 n 以内质数或者所有最小质因子大于 Primej 的合数的 k 次方和

转移就是删掉最小质因子为 j 的数,具体如下:

gk(n,j)={gk(n,j1)pjk×(gk(npj,j1)gk(pj1,j1))[pj2n]gk(n,j1)                                                                      [pj2>n]

第一种转移乘的 pjk 表示贡献,考虑 ik 是完全积性函数,所以直接除不用管互质

最后减去 g(pj1,j1) 表示删去质数在 g(n,k) 中的贡献

关注到转移的时候对于 [1,n] 的每个数只关注其 nPrime 的值,那么先 n 处理出来所有 ni 的结果

看起来这样子计算的时候只关注了每个整除区间右端点的 dp 值,如果随机访问怎么办?

不难发现所求是 gk(n,n),根据转移方程求解的时候只需要 Nx,所以预处理 ni 的结果即可

这部分的复杂度分析大可直接计算循环了多少次,这个复杂度甚至和杜教筛的分析完全一样,只是由于只扫描预处理了的质因子,那么要除一个 log

Part II: DP

接下来设 S(n,x) 表示 n 以内所有最小质因子大于 Primex 的数的 函数值 之和,g(n) 表示 n 范围内所有质数函数值之和,sumx 表示前 x 个质数的函数值之和

根据定义:序号 x 的质数并不计算在 S(,x) 中。所以答案是 S(n,0)

转移如下:

S(n,x)=g(n)sumx+pkcn,k>xf(pkc)×(S(npkc,k)+[c1])

含义其实是比较显然的,注意后面的求和又一次使用了积性函数的性质,加 [c1] 的含义是如果此时不为 1 还需要把 pc 的函数值加入答案,非常巧妙

直接递归统计贡献即可,也不需要记忆化

实现

先预处理每个质数处的前缀和和每个 gk(x,0),根据具体含义,这些东西都是可以通过次方前缀和来做出来的

不难发现对于高次自然数幂前缀和,需要伯努利数或者插值,这也就是处理高次的时候会复杂的原因

处理 gk(x,0) 时只处理每个整除区间右端点的函数前缀和,然后做第一部分的 DP(直接对着式子板抄即可)

对于第二部分中的 g(n)sump 这个直接对着多项式做减法即可,剩下的都是裸的实现,无需赘述

一些细节:

  • 整除分块的数组要开 2 倍,原因是 nx 的结果要分成 nn 的两部分

  • 处理 1 的贡献通常的办法是都删掉,最后加上一个 f(1)

代码是模板题 LOJ6053

const int N=2e5+10,mod=1e9+7,inv2=5e8+4;
int fl[N],pri[N],num,tot,bl,n,id1[N],id2[N],g2[N],g1[N],s1[N],R[N]; 
inline int get(int x){return x<=bl?id1[x]:id2[n/x];}
inline int calc(int x,int y){
    if(x<=1||pri[y]>x) return 0;
    int pos=get(x),ans=del(del(g2[pos],g1[pos]),(s1[y-1]+1-y+mod)%mod);
    if(y==1) ans=add(ans,2); 
    for(int i=y;i<=num&&pri[i]*pri[i]<=x;++i){
        int prod=pri[i];
        for(int j=1;prod*pri[i]<=x;prod*=pri[i],++j){
            ans=add(ans,add(mul(calc(x/prod,i+1),pri[i]^j),pri[i]^(j+1)));
        } 
    } return ans;
}
signed main(){
    n=read(); bl=sqrt(n);  
    for(int i=2;i<=bl;++i){
        if(!fl[i]) pri[++num]=i,s1[num]=add(s1[num-1],i);
        for(int j=1;j<=num&&pri[j]*i<=bl;++j){
            fl[i*pri[j]]=1; if(i%pri[j]==0) break;
        } 
    }
    for(int l=1,r;l<=n;l=r+1){
        r=(n/(n/l)); R[++tot]=n/l;
        // R[i] 记录区间右端点
        g1[tot]=del(R[tot]%mod,1); 
        g2[tot]=mul(add(R[tot]%mod,2),mul(del(R[tot]%mod,1),inv2));
        if(n/l<=bl) id1[n/l]=tot; else id2[r]=tot; 
    } 
    for(int i=1;i<=num;++i){
        for(int j=1;pri[i]*pri[i]<=R[j];++j){
            int k=get(R[j]/pri[i]);
            // 因为 (n/a)/b) = (n/(ab)) 那么可以直接找 id
            g1[j]=del(g1[j],del(g1[k],i-1));
            g2[j]=del(g2[j],mul(pri[i],del(g2[k],s1[i-1])));
        } 
    } printf("%lld\n",add(1,calc(n,1))); 
    return 0;
}

例题

LOJ6625

当头一棒

这题目告诉我们 min25 筛不一定要写第二部分,(那么这题应该放到 DP 杂题……)

考虑合数处的取值是 minp(i)2,那么使用第一部分的 DP 能统计出来每个质数作为多少质数的 minp

这种题对于博主这种一根筋的废物还是很有用的!

LOJ3069

现场居然能过掉 4 个人,候选队实力恐怖如斯

面向数据范围不难考虑对 f(x) 是否是积性函数进行思考,而根据定义我们发现圆上的整点是平均分布在四个象限里面的,那么对 f(i)/4 打表

好耶,是积性函数!但是如何证明呢?

首先证明两个结论:

  • 奇质数 x 可以被分解成 a2+b2 当且仅当 x1 (mod 4)

    根据二次剩余的一些理论,得到此时存在一个 a21(mod x)

    那么构造 as1+t1as2+t2modp 满足 s1,s2,t1,t2p

    此时满足条件的的 x=|s1s2|,y=|t1t2|,证明带入即可,那么发现 x2+y20modp,x2+y2p

定义高斯整数 x+yi[x,yInterger] 和其范数 N(x+yi)=x2+y2

同时定义高斯质数为不能表示为若干个高斯质数乘积的数,这里使用上面的结论,发现高斯质数就是 p3mod4 的质数

定义两个不同的高斯整数是相互伴随的当且仅当一个通过旋转 90,180,270 可以成为另一个

考虑这样一个性质:对于高斯整数,也存在唯一分解定律,这里分解出来的结果每个数可以替换为其伴随

这个性质考虑归奶证明,使用范数的乘积可以快速得到

那么这个函数是积性函数就完成了证明,同时不难发现质数处取值:

g(pk)={(2p+1)k[p1mod4]1              [p3mod4]

剩下的部分不难使用 min25 筛进行求解,但是一个需要分开讨论

在进行 min25 筛的第一个 DP 的过程中,使用 3×31 的优美性质来进行 p3mod4 的部分的转移

具体实现的时候分 g1(n,i),g2(n,i) 两个部分进行转移,一开始转移的都是个数,并不计算具体点值

剩下的都交给第二个 DP 啦,细节并不多,复杂度 Θ(n0.75logn)

posted @   没学完四大礼包不改名  阅读(147)  评论(1编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示