莫比乌斯反演——从入门到入坟
(注意!本人刚学莫反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函数为完全数论函数。
重要的数论函数有:
其中,莫比乌斯函数有一个超级important的性质:
然后,是莫比乌斯反演中十分甚至九分重要的:狄利克雷卷积!
它的定义是这样的:狄利克雷卷积是一种运算,符号记作 * ,他表示两个函数关系的卷积,具体形式如下:
若 \(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 莫比乌斯反演的基本概念与应用
首先来看一道题目:
我们都知道,求\(\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]\)的判断。
那么原式可以变成:
显然,当\(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。具体过程如下:
此时,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)\)的时间复杂度。
接下来在看一道变式:
聪明的你一定想到啦:我们只需要把 \(i,j\) 缩小 \(k\) 倍就可以啦!
于是原式变为:
化简跟前面一样,你一定可以的!我放一下最后的答案吧:\(\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)\)
根据这种想法,我们就能将原式变成:
接下来我们就可以像之前一样做啦!
先把 \(k\) 提前,
发现后面那部分跟之前做的那题很像,于是直接变成:
现在,又是莫比乌斯反演的一个常见套路:
那么用 \(\frac{T}{k}\) 代替掉 \(d\) ,并且把 \(k\) 移进去,原式:
像之前一样,将枚举 \(k\) 变为枚举 \(T\) , 原式:
我们在Part1中提到过,\(\varphi=id*\mu\),这时我们看见 \(\sum\limits_{k|T}k\mu(\frac{T}{k})\) 不就刚好是 \(id*\mu\) 吗!于是原式:
我们就成功做完啦!时间复杂度优化到了 \(O(n)\)呢!
最后一题!如果你都会了就毕业了33%了!剩下的靠你自己啦!(后面一部分有优化以及之前说到的线性筛的技巧,不要离开哦!)
输入 \(n\) , 求 \(\sum\limits_{i=1}^n \sum\limits_{j=1}^n ij\gcd(i,j)\)
原题选自洛谷题目P3678 简单的数学题,原题用我们这里讨论的方法是会 TLE+MLE 的,具体请理解后看Part3的优化哦。
我们可以跟上一题一样,先枚举一个 \(k\),原式:
注意,如果我们把 \(i,j\) 都像之前一样缩小 \(k\) 倍,那么 \(ij\) 就会缩小 \(k^2\) 倍,所以我们要乘回来哦。于是原式变为:
然后像之前一样变出 \(d\),注意这里 \(d\) 也要乘回来哦,乘个 \(d^2\),原式:
如同上一题,相同套路,令 \(T=kd\),并用 \(\frac{T}{k}\) 代替 \(d\),原式:
相信你前面好好学,这里一定能懂的!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\)
我们通过莫反等可以得到:
后面跟普通莫反相同套路啦,变成枚举 \(d\) 即可。你也可以上网看看完整证明,我这里就不放了,直接给结论吧。
因为我们想要前缀和,即 \(S(n)\),因此,我们把 \(d=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
好啦!莫比乌斯反演终于讲完啦!其实有一些不足的地方,比如没有很严谨的证明,证明在网上是能找到的,这篇文章主要是注重于它的应用方面。如果我的难懂,可以告诉我,我尽力讲的更清楚一点!

浙公网安备 33010602011771号