莫比乌斯反演——从入门到入坟

(注意!本人刚学莫反0.01s,如有错误请及时提出!本文大部分题目出自洛谷网站)

莫比乌斯反演是信竞中重要的一个数论知识,可以将一些运算从\(O(n^2)\)优化到O(n)甚至\(O(\sqrt n)\)的速度,本文将尽量详细地从前置知识到普通莫反的全过程进行讲解。

Part1 前置知识


首先介绍一下数论函数:若\(\forall a,b\in N^*\),满足\(\gcd(a,b)=1\),即\(a,b\)互质,有一个函数f,使得\(f(ab)=f(a)f(b)\),那么f函数就是数论函数。
特殊地,如果\(\gcd(a,b)\neq 1\),仍有\(f(ab)=f(a)f(b)\),则f函数为完全数论函数。

重要的数论函数有:

\[\text{单位函数:}\varepsilon(n)=[n=1]\text{(完全数论函数)} \]

\[\text{恒等函数:}id_k(n)=n^k,id_1\text{常记为:}id\text{(完全数论函数)} \]

\[\text{常数函数:}1(n)=1\text{(完全数论函数)} \]

\[\text{除数函数:}\sigma_k(n)=\sum_{d|n}d^k,\sigma_0(n)\text{常记为}d(n)\text{或}\tau(n),\sigma_1(n)\text{常记为}\sigma(n) \]

\[\text{欧拉函数:}\varphi(n)=\sum_{i=1}^n[\gcd(i,n)=1] \]

\[\text{莫比乌斯函数:}\mu(n)=\begin{cases} 0 & {\exists d>0,d^2|n}\\ 1 & {n=1}\\ (-1)^{\omega(n)} & {otherwise} \end{cases} \text{其中,}\omega(n)\text{表示n的质因子个数} \]

其中,莫比乌斯函数有一个超级important的性质:

\[\sum_{d|n}\mu(d)=\begin{cases} 1 & {n=1}\\ 0 & {otherwise} \end{cases} \]

然后,是莫比乌斯反演中十分甚至九分重要的:狄利克雷卷积
它的定义是这样的:狄利克雷卷积是一种运算,符号记作 * ,他表示两个函数关系的卷积,具体形式如下:

\(f,g\) 为数论函数,则 \((f*g)(i)=\sum\limits_{d|i}g(d)f(\frac{i}{d})\) ,注意:卷积后的 \((f*g)\) 函数也是数论函数。

通过狄利克雷卷积,我们就能证明出许多性质:
1.狄利克雷卷积有交换律、结合律。
2.\(id_k*1=\sigma_k\)
3.欧拉反演:\(n=\sum\limits_{d|n}\varphi(d)\)
4.\(\varepsilon*f=f\),其中 \(f\) 为数论函数

其中,第三条是非常常用的一个公式,它也可以变成\(id=\varphi*1\)\(\varphi=id*\mu\)


Part2 莫比乌斯反演的基本概念与应用


首先来看一道题目:

\[\text{输入一个数n,求:}\sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=1] \]

我们都知道,求\(\gcd(i,j)\)的时间复杂度为\(O(\log n)\),那么如果直接枚举每一个i,j,则总时间复杂度为\(O(n^2\log n)\),在这类题中,n基本上为1e7~1e8左右,在1s的时限中是绝对跑不过的,那么此时就需要借助莫比乌斯反演了!

首先,我们观察莫比乌斯函数的性质,会发现,如果我们令\(n\),变成这道题目中的\(\gcd(i,j)\),就恰好满足了\([\gcd(i,j)=1]\)的判断。
那么原式可以变成:

\[\sum_{i=1}^n\sum_{j=1}^n\sum_{d|\gcd(i,j)}\mu(d) \]

显然,当\(d|\gcd(i,j)\)时,必定是\(d|i\text{且}d|j\),原式变为:\(\sum\limits_{i=1}^n\sum\limits_{j=1}^n\sum\limits_{d|i\text{且}d|j}\mu(d)\)

接下来是莫比乌斯反演中最关键的一步:我们将枚举 i , j , 计算约数 d , 变成枚举每一个数 d , 计算 i , j。具体过程如下:

\[\text{原式=}\sum_{d=1}^n\sum_{d|i}^n\sum_{d|j}^n\mu(d) \]

此时,d已经不由 i , j 决定,因此可以将\(\mu(d)\)提前,变为:\(\sum\limits_{d=1}^n\mu(d)\sum\limits_{d|i}^n\sum\limits_{d|j}^n1\)

又因为在\([1,n]\)中,d的倍数一共有\(\lfloor\frac{n}{d}\rfloor\)个,所以\(\sum\limits_{d|i}^n\sum\limits_{d|j}^n1=\lfloor\frac{n}{d}\rfloor^2\)(想一想,为什么)

此时,我们已经完成这道题目啦!整理一下,原式\(=\sum\limits_{d=1}^n\mu(d)\lfloor\frac{n}{d}\rfloor^2\),对于\(\mu(d)\),我们可以通过欧拉筛(线性筛)筛出来,这一点我们后面再说。

现在你懂了吗?这些式子,我们只需要把他变为一些简单的数论函数(主要是 \(\mu\)\(\varphi\) 较常用),就可以实现\(O(n)\)的时间复杂度。


接下来在看一道变式:

\[\text{输入}n,k\text{,求:}\sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=k] \]

聪明的你一定想到啦:我们只需要把 \(i,j\) 缩小 \(k\) 倍就可以啦!
于是原式变为:

\[\sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{k}\rfloor}[\gcd(i,j)=1] \]

化简跟前面一样,你一定可以的!我放一下最后的答案吧:\(\sum\limits_{d=1}^n\mu(d)\lfloor\frac{n}{kd}\rfloor^2\)

PS : 如果输入的是 \(n,m,k\) , 求 \(\sum\limits_{i=1}^n\sum\limits_{j=1}^m[\gcd(i,j)=k]\),我们通过一点点理解,就不难发现,化简之后其实会变成:\(\sum\limits_{d=1}^{\min(n,m)}\mu(d)\lfloor\frac{\min(n,m)}{kd}\rfloor^2\)
这就是洛谷题目P4450 双亲数以及P3455 ZAP-Queries


让我们稍微提高点难度!

输入\(n\),求\(\sum\limits_{i=1}^n\sum\limits_{j=1}^n\gcd(i,j)\)
选自洛谷题目P1390 公约数的和,计算后的结果需要进行一些改动,具体可以看看题解,在这里只讨论这个主要的问题如何解决。

第一眼你可能不会,但是再看一下,你确实不会你就能发现一个事情:与其算 \(gcd(i,j)\) ,不如枚举 \([1,n]\) 中的每一个数,看这个数是不是等于 \(\gcd(i,j)\)

根据这种想法,我们就能将原式变成:

\[\sum_{k=1}^n\sum_{i=1}^n\sum_{j=1}^nk[\gcd(i,j)=k] \]

接下来我们就可以像之前一样做啦!
先把 \(k\) 提前,

\[\sum_{k=1}^nk\sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=k] \]

发现后面那部分跟之前做的那题很像,于是直接变成:

\[\sum_{k=1}^nk\sum\limits_{d=1}^n\mu(d)\lfloor\frac{n}{kd}\rfloor^2 \]

现在,又是莫比乌斯反演的一个常见套路:

\[\text{令}T=kd \]

那么用 \(\frac{T}{k}\) 代替掉 \(d\) ,并且把 \(k\) 移进去,原式:

\[=\sum_{k=1}^n\sum_{k|T}^nk\mu(\frac{T}{k})\lfloor\frac{n}{T}\rfloor^2 \]

像之前一样,将枚举 \(k\) 变为枚举 \(T\) , 原式:

\[=\sum_{T=1}^n\sum_{k|T}k\mu(\frac{T}{k})\lfloor\frac{n}{T}\rfloor^2 \]

我们在Part1中提到过,\(\varphi=id*\mu\),这时我们看见 \(\sum\limits_{k|T}k\mu(\frac{T}{k})\) 不就刚好是 \(id*\mu\) 吗!于是原式:

\[=\sum_{T=1}^n\varphi(T)\lfloor\frac{n}{T}\rfloor^2 \]

我们就成功做完啦!时间复杂度优化到了 \(O(n)\)呢!


最后一题!如果你都会了就毕业了33%了!剩下的靠你自己啦!(后面一部分有优化以及之前说到的线性筛的技巧,不要离开哦!)
输入 \(n\) , 求 \(\sum\limits_{i=1}^n \sum\limits_{j=1}^n ij\gcd(i,j)\)
原题选自洛谷题目P3678 简单的数学题,原题用我们这里讨论的方法是会 TLE+MLE 的,具体请理解后看Part3的优化哦。
我们可以跟上一题一样,先枚举一个 \(k\),原式:

\[=\sum_{k=1}^nk\sum_{i=1}^n\sum_{j=1}^nij[\gcd(i,j)=k] \]

注意,如果我们把 \(i,j\) 都像之前一样缩小 \(k\) 倍,那么 \(ij\) 就会缩小 \(k^2\) 倍,所以我们要乘回来哦。于是原式变为:

\[=\sum_{k=1}^nk^3\sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{k}\rfloor}ij[\gcd(i,j)=1] \]

然后像之前一样变出 \(d\),注意这里 \(d\) 也要乘回来哦,乘个 \(d^2\),原式:

\[=\sum_{k=1}^nk^3\sum_{d=1}^n\mu(d)d^2\sum_{i=1}^{\lfloor\frac{n}{k d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{k d}\rfloor}ij \]

如同上一题,相同套路,令 \(T=kd\),并用 \(\frac{T}{k}\) 代替 \(d\),原式:

\[=\sum_{k=1}^nk^3\sum_{k|T}^n\mu(\frac{T}{k})(\frac{T}{k})^2\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}ij \]

\[=\sum_{T=1}^n\sum_{k|T}k^3\mu(\frac{T}{k})(\frac{T}{k})^2\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}ij \]

\[=\sum_{T=1}^n\sum_{k|T}k\mu(\frac{T}{k})T^2\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}ij \]

\[=\sum_{T=1}^nT^2\sum_{k|T}k\mu(\frac{T}{k})\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}ij \]

\[=\sum_{T=1}^nT^2\varphi(T)\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}\sum_{i=1}^{\lfloor\frac{n}{T}\rfloor}ij \]

相信你前面好好学,这里一定能懂的!Part3 是优化的许多方法,可以大幅降低时间复杂度哦。


Part3. 莫比乌斯反演的常用优化

在上一Part中,我们已经看见了莫比乌斯反演将时间复杂度\(O(n^2)\)降到\(O(n)\)的神奇操作,那么有什么办法进一步优化呢?这一Part将详细介绍!
常用优化技巧有:

  • 预处理(线性筛、前缀和)
  • 杜教筛、Min-25筛、州阁筛(后面两个我也不会qwq)
  • 整除分块(我认为最重要的一个优化!)

我们首先来介绍一下:线性筛(欧拉筛)!
我们注意到全篇讨论的都是数论函数,而所有的积性函数都是可以在线性时间内筛出来的!
这里将提供线性筛欧拉函数 \(\varphi\) 、莫比乌斯函数 \(\mu\) 的模板,具体证明请另行查找(证明资料找不到了!qwq)

const int maxn=4e4;
int prime[maxn+5],cnt;
bool vis[maxn+5];
int phi[maxn+5];
// 欧拉函数就是在 [1,n] 中跟 n 互质的数字的个数
void get_phi()
{
    phi[1]=1;
    for(int i=2;i<=maxn;i++)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            phi[i]=i-1;//质数肯定跟前面i-1个数字都互质
        }
        for(int j=1;j<=cnt&&i*prime[j]<=maxn;j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            phi[i*prime[j]]=phi[i]*phi[prime[j]];
            // 这就是背模板啦,上面这一行还有一种是:
            //phi[i*prime[j]]=phi[i]*(prime[j]-1);
            //这两种都是可以的
        }
    }
}
const int maxn=1e7+5;
bool vis[maxn];
int prime[maxn];//筛质数 质数的莫比乌斯函数值=-1
int mu[maxn];
int cnt;
void get_mu()
{
    mu[1]=1;
    for(int i=2;i<=maxn;i++)
    {
        if(!vis[i])
        {
            mu[i]=-1;
            prime[++cnt]=i;
        }
        for(int j=1;j<=cnt&&prime[j]*i<=maxn;j++)
        {
            vis[prime[j]*i]=1;
            if(i%prime[j]==0)break;
            else mu[i*prime[j]]=-mu[i];
        }
    }
    return ;
}

以上就是线性筛 \(\varphi\)\(\mu\) 的模板啦!


讲完了线性筛质数,但是如果去洛谷看看讲的最后一题(Link P3768 简单的数学题),就会发现它的范围是 \(n\le 10^{10}\),我们既不可能开一个1e10大小的phi数组,也不可能在4s内筛出来。
这时我们就需要一个更高效的方法!没错!他就是——杜教筛!(州阁筛、Min-25筛更好,但是我不会qwq)它可以在\(O(n^{\frac{3}{4}})\)的时间里筛出来积性函数的前缀和(而州阁筛、Min-25筛的时间复杂度为\(O(\frac{n^\frac{3}{4}}{log n})\),更加优秀)

杜教筛推导比较简单,基本上是莫反的推导。
假设我们想推导的积性函数为 \(f\) ,记 \(S\)\(f\) 的前缀和,即\(S(n)=\sum\limits_{i=1}^nf(i)\)
然后我们构建两个积性函数 \(h,g\),令 \(h=g*f\)
我们通过莫反等可以得到:

\[\sum_{i=1}^nh(i)=\sum_{i=1}(g*f)(i) =\sum_{i=1}^n\sum_{d|i}g(d)f(\frac{i}{d}) \]

后面跟普通莫反相同套路啦,变成枚举 \(d\) 即可。你也可以上网看看完整证明,我这里就不放了,直接给结论吧。

\[\sum_{i=1}^nh(i)=\sum_{d=1}^ng(d)S(\lfloor\frac{n}{d}\rfloor) \]

因为我们想要前缀和,即 \(S(n)\),因此,我们把 \(d=1\) 这一项拿出来,

\[\sum_{i=1}^nh(i)=g(1)S(n)+\sum_{d=2}^ng(d)S(\lfloor\frac{n}{d}\rfloor) \]

所以我们就得到了:

\[S(n)=\frac{\sum_{i=1}^nh(i)-\sum_{d=2}^ng(d)S(\lfloor\frac{n}{d}\rfloor)}{g(1)} \]

然后就可以递归求解啦!因为这里涉及到接下来讲的整除分块,因此这个代码在下一个那里哦!


前面讲完了预处理如何更快,但是计算最后的循环还是要计算 \(n\) 次啊,对于1e10的数据我们还是无能为力,那该怎么办呢?这就是一个减少计算次数的方法——整除分块(数论分块)!
整除分块是利用了这样一个特性:在一个数 \(n\) 中,有不同的因子,而相隔因子之间的 \(\lfloor\frac{n}{i}\rfloor\) 的值是一样的。我们来举个例子:
当 n=10 时:
我们枚举 \([1,n]\) 的每一个数 \(i\) ,计算 \(\lfloor\frac{n}{i}\rfloor\) , 观察他们的特点:

i n/i i是否为n的因子
1 10
2 5
3 3
4 3
5 2
6 2
7 2
8 2
9 2
10 1

不难发现吧!每一个因子之间,\(\lfloor\frac{n}{i}\rfloor\) 的值都是一样的!
这里给出了整除分块的代码,你可以试试理解,然后应用哦!(用多了就会突然明白它的原理了,直接看懂了当我没说……)

int funtion(int n)
{
    // 整除分块(数论分块)
    // 计算 i=[1,n]的 n/i 值
    int ans=0;
    for(int l=1,r;l<=n;l=r+1)
    {
        r=n/(n/l);//这里的 r,l 就是两个相邻的因子,理解一下为什么 r 要这样算
        ans+=(r-l+1)*(n/l);//在 (r-l+1) 这个长度中每个数都是一样的,都是 n/l,不懂可以结合上面的表看一下哦
    }
    return ans;
}

接下来就是:杜教筛的代码!把这个讲完,这篇文章就结束了!
首先是筛欧拉函数的:
欧拉函数的杜教筛利用 \(\varphi*I=id\) 这个公式。

typedef long long ll;
unordered_map<int,ll>ans_phi;//用unordered_map存,节省空间,而且比map更好
const int maxn=1e7;
int prime[maxn+5],cnt;
bool vis[maxn+5];
ll phi[maxn+5];//要先筛出较小范围的phi值,才能用杜教筛,大约筛出 n^(2/3) 就差不多
void pretreat()
{
    phi[1]=1;
    for(int i=2;i<=maxn;i++)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=maxn;j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                phi[i*prime[j]]=1ll*prime[j]*phi[i];
                break;
            }
            phi[i*prime[j]]=1ll*phi[i]*phi[prime[j]];
        }
    }
    for(int i=1;i<=maxn;i++)
    {
        phi[i]+=phi[i-1];//这里把单个phi值变成了phi的前缀和哦
    }
}
ll ask_phi(ll n)
{
    if(n<=maxn)return phi[n];//如果之前已经筛出来了,那就可以直接返回了
    if(ans_phi[n])return ans_phi[n];//类似于记忆化搜索,之前算过也直接返回
    // 这个位置利用了杜教筛的公式,因为φ*I=id,所以我们令 g=I , h=id ,他们的前缀和很明显
    ll res=(1ll+n)*n>>1; //这是杜教筛公式开头的[1,n]的h的前缀和,因为 h=id , 所以 h(x)=x ,那么很明显 [1,n] 的前缀和就是 n*(1+n)/2
    for(ll l=2,r;l<=n;l=r+1)
    {
        // 记得从2开头哦
        r=n/(n/l);
        res-=1ll*(r-l+1)*ask_phi(n/l);//这里是利用了整除分块的原理,是杜教筛公式最后减去的[2,n]的 g(d)S(n/d) , 利用了递归求解
    }
    return ans_phi[n]=res;//最后这里应该是 res/g(1),但是 g=I , 所以 g(x)=1 就直接忽略了  (记得要给 map赋值啊,不然下次又要重新筛了!)
}

然后是筛莫比乌斯函数的:(其实跟欧拉函数的大差不差,主要难度都在找哪两个函数上了,这里用的是 \(\varepsilon*\mu=1\)

typedef long long ll;
unordered_map<int,ll>ans_mu;
const int maxn=1e7;
int prime[maxn+5],cnt;
bool vis[maxn+5];
ll mu[maxn+5];
void pretreat()
{   
    mu[1]=1;
    for(int i=2;i<=maxn;i++)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=maxn;j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            {
                break;
            }
            mu[i*prime[j]]=-mu[i];
        }
    }
    for(int i=1;i<=maxn;i++)
        mu[i]+=mu[i-1];
}
ll ask_mu(ll n)
{
    if(n<=maxn)return mu[n];
    if(ans_mu[n])return ans_mu[n];
    ll res=1; // 因为ϵ的前缀和就是1(只有ϵ(1)=1,其他都是0)
    for(ll l=2,r;l<=n;l=r+1)
    {
        r=n/(n/l);
        res-=(r-l+1)*ask_mu(n/l);//其他都跟欧拉函数差不多
    }
    return ans_mu[n]=res;
}

END

好啦!莫比乌斯反演终于讲完啦!其实有一些不足的地方,比如没有很严谨的证明,证明在网上是能找到的,这篇文章主要是注重于它的应用方面。如果我的难懂,可以告诉我,我尽力讲的更清楚一点!

Thanks for your reading !

posted @ 2023-06-17 03:16  Nicly  阅读(560)  评论(2)    收藏  举报