杜教筛学习笔记

杜教筛学习笔记

杜教筛被用于求解某一数论函数 f 的前缀和,即对于形如 S(n)=i=1nf(i) 形式的函数 S,杜教筛能够在小于线性复杂度的复杂度内求解。

算法思想

尝试构造一个函数 S 的递推式。选择一个数论函数 g,那么根据狄利克雷卷积可以得到:

i=1n(fg)(i)=i=1nd|ig(d)f(id)=d=1ng(d)i=1ndf(i)=i=1ng(i)S(ni)

那么接着我们就可以简单的推得:

g(1)S(n)=i=1ng(i)S(ni)i=2ng(i)S(ni)=i=1n(fg)(i)i=2ng(i)S(ni)

不难看出,只需要能够快速求解 i=1n(fg)(i)i=2ng(i)S(ni),就可以快速求出 g(1)S(n),也能够快速求得 S(n)

事实上,不论 f 是否是积性函数,只要构造出的函数 g 能够满足上面的式子的推导,就可以使用杜教筛进行求解。

时间复杂度

在不能预处理的情况下,杜教筛的复杂度是 O(n34) 的。

而在可以进行预处理的情况下,进行前 n23 个数的预处理能够让时间复杂度降至 O(n23)

更加值得注意的是,上述时间复杂度全部建立在记忆化的基础上,因此需要采用 mapunordered_map 进行存储。

常见积性函数及其卷积形式

ε=μ1d=11σ=id1φ=μid

其中,ε 单位函数,1 是常函数,d 是因子函数,σ 是除数函数,id 是恒等函数。

代码实现

下面以 μ 函数的杜教筛为实例代码。那么所求 S(n)=i=1nμ(i)

不妨令 g=1,所以:

g(1)S(n)=S(n)=i=1n(μ1)(i)i=2ng(i)S(ni)=i=1nε(i)i=2nS(ni)=1i=2nS(ni)

不难看出后半部分可以用数论分块求解。

int cnt;
int p[N+5],mu[N+5];
bool vis[N+5];
unordered_map<int,ll>sum_mu;

void EulerS(){
    mu[1]=1;
    for(int i=2;i<=N;i++){
        if(!vis[i]){
            p[++cnt]=i;
            mu[i]=-1;
        }
        for(int j=1;p[j]<=N/i;j++){
            vis[i*p[j]]=true;
            if(i%p[j]==0){
                mu[i*p[j]]=0;
                break;
            }
            mu[i*p[j]]=-mu[i];
        }
    }
    for(int i=1;i<=N;i++)mu[i]+=mu[i-1];
    return ;
}//线性筛预处理

ll SumMu(ll n){
    if(n<=N)return mu[n];//线性筛预处理出的答案可以不用求解
    if(sum_mu[n])return sum_mu[n];//之前求解过的答案可以不用求解
    ll res=1;
    for(ll l=2,r;l<=n;l=r+1){
        r=n/(n/l);
        res-=SumMu(n/l)*(r-l+1);
    }//求解前缀和
    return sum_mu[n]=res;//记忆化剪枝
}
posted @   DycIsMyName  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示