欧拉函数&莫比乌斯函数&狄利克雷卷积&杜教筛

· · 算法·理论

欧拉函数

定义

定义 \varphi(n) 表示小于等于 n 的正整数中与 n 互质数的数量。

基本性质

线性筛求欧拉函数

inline void Euler(){
    phi[1]=1;
    for(int i=2;i<=5e6;i++){
        if(!is[i]){
            prime[++cnt]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=cnt&&prime[j]*i<=5e6;j++){
            is[prime[j]*i]=true;
            if(i%prime[j]==0){
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
}

莫比乌斯函数

定义

\mu(i)=\begin{cases}1& n=1\\ 0 & n含有平方因子\\ (-1)^k & k为n本质不同的质因子个数\end{cases}

性质

线性筛求莫比乌斯函数

inline void Euler(){
    mu[1]=1;
    for(int i=2;i<=2e7;i++){
        if(!is[i]){
            prime[++cnt]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=cnt&&prime[j]*i<=2e7;j++){
            is[i*prime[j]]=true;
            if(i%prime[j]==0){
                mu[i*prime[j]]=0;
                break;
            }
            mu[i*prime[j]]=-mu[i];
        }
    }
}

习题

P2522 [HAOI2011] Problem b

不难想到把这个要求的东西看做一个 (a,c)\rarr(b,d) 的矩形,直接二维前缀和一样的做差将其分为 4(1,1)\rarr(n,m) 的块计算。 对于一个块其贡献推导如下:

\sum\limits_{x=1}^n\sum\limits_{y=1}^m [\gcd(x,y)=k] $=\sum\limits_{x=1}^{\lfloor \frac{n}{k}\rfloor}\sum\limits_{y=1}^{\lfloor \frac{m}{k}\rfloor}\sum\limits_{d|\gcd(x,y)}\mu(d)$ (**性质 $2$**) $=\sum\limits_{d=1}^{\min(\lfloor \frac{n}{k}\rfloor,\lfloor \frac{m}{k}\rfloor)} \mu(d) \sum\limits_{x=1}^{\lfloor \frac{n}{kd}\rfloor}\sum\limits_{y=1}^{\lfloor \frac{m}{kd}\rfloor}1$(针对 $\mu$ 算贡献) $=\sum\limits_{d=1}^{\min(\lfloor \frac{n}{k}\rfloor,\lfloor \frac{m}{k}\rfloor)} \mu(d)\cdot \lfloor \frac{n}{kd}\rfloor \cdot \lfloor \frac{m}{kd}\rfloor$ (化简) 式子推导到这里,就结束了,具体计算直接根号分块就可以了,预处理出来 $\mu$ 的前缀和。(有多测,所以要根号分块) [AC记录](https://www.luogu.com.cn/record/210836455) ### P3327 [SDOI2015] 约数个数和 对于两个数相乘的约数个数这个东西非常难以维护,想考虑将其化简。 $d(ij) $=\sum\limits_{x|i}\sum\limits_{y|j}\sum\limits_{d|\gcd(x,y)}\mu(d)$ (**性质 $2$**) $=\sum\limits_{d|i,d|j}\mu(d)\sum\limits_{x|i}\sum\limits_{y|j}[d|\gcd(x,y)]$(针对 $\mu$ 算贡献) $=\sum\limits_{d|i,d|j}\mu(d)\sum\limits_{x|\frac{i}{d}}\sum\limits_{y|\frac{j}{d}}1$ (化简) $=\sum\limits_{d|i,d|j}\mu(d)\cdot d(\frac{i}{d})\cdot d(\frac{j}{d})$ (化简,缩小规模) 把推导的式子代入原式: $\sum\limits_{i=1}^n\sum\limits_{j=1}^m d(ij) $=\sum\limits_{d=1}^{\min(n,m)}\mu(d) \sum\limits_{i=1}^{\lfloor \frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor \frac{m}{d}\rfloor}d(i)d(j)$ (针对 $\mu$ 算贡献) $=\sum\limits_{d=1}^{\min(n,m)}\mu(d) \sum\limits_{i=1}^{\lfloor \frac{n}{d}\rfloor}d(i)\sum\limits_{j=1}^{\lfloor \frac{m}{d}\rfloor}d(j)$ (整理) 钦定 $S(n)=\sum\limits_{i=1}^n d(i)$。 $=\sum\limits_{d=1}^{\min(n,m)}\mu(d)\cdot S(\lfloor \frac{n}{d}\rfloor)\cdot S(\lfloor \frac{m}{d}\rfloor)

我们用线性筛维护出来 S(i) 然后又是熟悉的根号分块。

代码展示一下 S(i) 的求法:

// d数组一开始求每个数的约数个数,最后在前缀和与S数组内涵对齐。
// num 表示一个数最小质因数出现的次数
inline void Euler(){
    d[1]=1;
    for(int i=2;i<=5e4;i++){
        if(!is[i]){
            prime[++cnt]=i;
            num[i]=1;
            d[i]=2;
        }
        for(int j=1;j<=cnt&&prime[j]*i<=5e4;j++){
            is[prime[j]*i]=true;
            d[i*prime[j]]=d[i]*2;
            num[i*prime[j]]=1;
            if(i%prime[j]==0){
                d[i*prime[j]]=d[i]/(num[i]+1)*(num[i]+2);
                num[i*prime[j]]=num[i]+1;
                break;
            }
        }
    }
    for(int i=1;i<=5e4;i++)d[i]+=d[i-1];
}

AC记录

P12599 常数要较小

\sum\limits_{i=1}^n\sum\limits_{j=1}^n \mu(ij)ij $=\sum\limits_{i=1}^n\sum\limits_{j=1}^n \sum\limits_{d|\gcd(i,j)}\mu(d) \mu(i)\mu(j)ij$ () 钦定 $f(i)=\mu(i)i$。 $=\sum\limits_{i=1}^n f(i)\sum\limits_{j=1}^n f(j) \sum\limits_{d|\gcd(i,j)}\mu(d)$(**性质 $2$**) $=\sum\limits_{d=1}^n \mu(d)(\sum\limits_{i=1}^n [d|i]f(i))^2$(针对 $\mu$ 算贡献) $=\sum\limits_{d=1}^n \mu(d)(\sum\limits_{i=1}^{\lfloor \frac{n}{d}\rfloor} f(d\cdot i))^2$ (化简) 可以把所有 $n$ 离线从小到大排序,考虑 $n\rarr n+1$ 贡献的变化。对于 $d|(n+1)$ 的 $d$ 会发生贡献,我们可以预处理 $1\sim 10^6$ 每个数的约数,这个量级是调和级数也就是 $O(n \ln n)$。 空间复杂度:$O(n\ln n)$。 时间复杂度:$O(n\ln n+q\log q)$。 [AC记录](https://www.luogu.com.cn/record/218648573) # 狄利克雷卷积 ## 定义 对于两个数论函数 $f(x),g(x)$,则他们狄利克雷卷积后的结果 $h(x)$ 为 : $h(x)=\sum\limits_{d|x}f(d)g(\frac{x}{d})=\sum\limits_{ab=x}f(a)g(b)$。 这个式子可以简化为:$h=f*g$。 # 杜教筛 ## 作用 对于一个数论函数 $f$,杜教筛可以在低于线性的时间复杂度求解 $S(n)=\sum\limits_{i=1}^n f(i)$。 ## 思想 对于一个数论函数 $g$ 必定满足如下式子: $\sum\limits_{i=1}^n (f*g)(i) =\sum\limits_{i=1}^n\sum\limits_{d|i}g(i)f(\frac{i}{d})

钦定 S(n)=\sum\limits_{i=1}^n f(i)

=\sum\limits_{i=1}^n g(i)\cdot S(\lfloor \frac{n}{i}\rfloor)

如果我们要求 S(n),则可以根据这个式子列出一个方程。

g(1)S(n)=\sum\limits_{i=1}^n (f*g)(i)-\sum\limits_{i=2}^n g(i)\cdot S(\lfloor \frac{n}{i}\rfloor)

如果我们能构造出适当的数论函数 g 满足以下所有条件:

  1. 快速计算 \sum\limits_{i=1}^n (f*g)(i)
  2. 快速计算 g 的前缀和,并通过根号分块计算 \sum\limits_{i=2}^n g(i)\cdot S(\lfloor \frac{n}{i}\rfloor)

则可以进行杜教筛。

杜教筛过程如下:

  1. 线性筛预处理前 n^{\frac{2}{3}}S(i)
  2. 递归求 S(n),小于等于 n^{\frac{2}{3}} 部分直接调用,大于使用map记忆化并使用根号分块按上面的式子计算。

时空复杂度

作者不会积分,所以暂时不会证明。 贴一个证明:oi.wiki 。

空间复杂度:O(n^{\frac{2}{3}})

时间复杂度:O(n^{\frac{2}{3}})

习题

P4213 【模板】杜教筛

对于欧拉函数,将其按照上面的式子展开为:

g(1)S(n)=\sum\limits_{i=1}^n (\varphi*g)(i)-\sum\limits_{i=2}^n g(i)\cdot S(\lfloor \frac{n}{i}\rfloor)

欧拉函数的性质5 非常牛x,观察等式右端的狄利克雷卷积,直接令 g=1\sum\limits_{i=1}^n (\varphi*g)(i)=\sum\limits_{i=1}^n\sum\limits_{d|i}\varphi(i)g(\frac{i}{d})=\sum\limits_{i=1}^n i=\frac{n(n+1)}{2}

对于莫比乌斯函数,将其按照上面的式子展开为:

g(1)S(n)=\sum\limits_{i=1}^n (\mu*g)(i)-\sum\limits_{i=2}^n g(i)\cdot S(\lfloor \frac{n}{i}\rfloor)

莫比乌斯函数的性质1 非常牛x,观察等式右端的狄利克雷卷积,直接也令 g=1\sum\limits_{i=1}^n (\mu*g)(i)=\sum\limits_{i=1}^n\sum\limits_{d|i}\mu(i)g(\frac{i}{d})=\sum\limits_{i=1}^n\sum\limits_{d|i}\mu(i)=\sum\limits_{i=1}^n[i=1]=1

直接开写,放一份代码。

//Syt forever!
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int maxn=2e7+10;
int T,n,cnt;
int mu[maxn],phi[maxn],prime[maxn];
bool is[maxn];
map<int,int>umu,uphi;
inline void Euler(){
    mu[1]=1;
    phi[1]=1;
    for(int i=2;i<=2e7;i++){
        if(!is[i]){
            prime[++cnt]=i;
            phi[i]=i-1;
            mu[i]=-1;
        }
        for(int j=1;j<=cnt&&prime[j]*i<=2e7;j++){
            is[i*prime[j]]=true;
            if(i%prime[j]==0){
                mu[i*prime[j]]=0;
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            mu[i*prime[j]]=-mu[i];
            phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
    for(int i=1;i<=2e7;i++){
        mu[i]+=mu[i-1];
        phi[i]+=phi[i-1];
    }
}
inline pair<int,int> get(int x){
    if(x<=2e7)return make_pair(phi[x],mu[x]);
    if(uphi[x])return make_pair(uphi[x],umu[x]);
    int ans_phi=x*(x+1)/2,ans_mu=1;
    for(int i=2,j;i<=x;i=j+1){
        j=n/(n/i);
        pair<int,int>p;
        p=get(x/j);
        ans_phi-=(j-i+1)*p.first;
        ans_mu-=(j-i+1)*p.second;
    }
    uphi[x]=ans_phi;
    umu[x]=ans_mu;
    return make_pair(ans_phi,ans_mu);
}
signed main(){
    Euler();
    scanf("%lld",&T);
    while(T--){
        scanf("%lld",&n);
        pair<int,int>p;
        p=get(n);
        printf("%lld %lld\n",p.first,p.second);
    }
    return 0;
}

这道题比较恶心的是杜教筛 10 次,map调用量过多,代码常数非常大,线性筛多预处理一些前缀和。

AC记录

P3768 简单的数学题

前置知识:\sum\limits_{i=1}^n i=\frac{n(n+1)}{2}\sum\limits_{i=1}^n i^2=\frac{n(n+1)(2n+1)}{6},\sum\limits_{i=1}^n i^3=(\sum\limits_{i=1}^n i)^2=\frac{n^2(n+1)^2}{4}

开始推式子。

\sum\limits_{i=1}^n\sum\limits_{j-1}^nij\gcd(i,j) $=\sum\limits_{k=1}^n \varphi(k)\sum\limits_{i=1}^{\lfloor \frac{n}{k}\rfloor}ik \sum\limits_{j=1}^{\lfloor \frac{n}{k}\rfloor}jk$ (针对 $\varphi$ 算贡献) $=\sum\limits_{k=1}^n \varphi(k)\cdot k^2 (\sum\limits_{i=1}^{\lfloor \frac{n}{k}\rfloor}i)^2

到这里发现这个式子后面的 (\sum\limits_{i=1}^{\lfloor \frac{n}{k}\rfloor}i)^2 能根号分块计算,现在的问题就是求 S(n)=\sum\limits_{k=1}^n \varphi(k)\cdot k^2 。将其按照杜教筛狄利克雷卷积形式展开如下:

g(1)S(n)=\sum\limits_{i=1}^n (f*g)(i)-\sum\limits_{i=2}^n g(i)\cdot S(\lfloor \frac{n}{i}\rfloor)

\sum\limits_{i=1}^n (f*g)(i) 展开为 \sum\limits_{i=1}^n \sum\limits_{d|i}\varphi(d)\cdot d^2 \cdot g(\frac{i}{d})

仿照模板题的方式,尝试让 g(i)=i^2 ,则会惊人的发现 \sum\limits_{i=1}^n (f*g)(i)=\sum\limits_{i=1}^n i^3。而 \sum\limits_{i=2}^n g(i)\cdot S(\lfloor \frac{n}{i}\rfloor) 也可以用前置知识+根号分块快速计算。

因为这道题是根号分块上分的部分求前缀和,所以其再调用根号分块会使用到之前的部分。直观感觉多次杜教筛和一次杜教筛没有区别,时间复杂度依然正确。

AC记录