莫比乌斯反演与数论函数
command_block · · 算法·理论
本博客的第一篇博文哦!纪念。
---- 莫比乌斯反演,本质是利用莫比乌斯函数与其他函数间卷积关系,对函数做一系列简化,从而更高效的解决问题。
---- 数论函数,更广阔的天地,因为\mu∈\text{数论函数}
0.写在前面
这里面式子可能比较多,大家多看看证明,尤其是关键证明以及题目推导过程,硬记下的结论越少,越不容易忘记。
(看不清式子可以放大网页)
由于写的时候匆忙,公式可能有错误,请私信或者在下方评论。
每个新东西提出后都会做题或者证明,权当熟悉。
不是一个下午就能看懂的,千万不要直接弃疗
- 给大家简要介绍一下符号的意思。
有时候以
有
1.狄利克雷卷积与数论函数
——介绍了狄利克雷卷积,数论函数(积性函数)的综合
换句话说就是
(不过一般不这样写,因为不方便化式子。)
比如说计算
可得
(对于某个新东西,能做到的话,理解的最好方式就是手动模拟)
狄利克雷卷积满足以下运算规律(显而易见,不证):
交换律——
结合律——
(狄利克雷卷积是一个对称的结构)
下面的推导都基于狄利克雷卷积,有必要好好理解。
介绍几个简单的数论函数,让你有个基本概念。
附上两个后面会讲的稍微复杂的例子:
这里的
把
(根据积性可以把
考虑数学归纳法(跟构造方法有点像)
设
我们要证明对于任意的
因为
-
1) a或b为1
当
a=b=1 时,e(1)=1=F(1)G(1) 由于积性,根据上文
F(1)=1 ,得G(1)=1 所以当
a 或b=1 时,结论显然成立。 -
2) ab>1
我们考虑已证明了所有的
a'<a;\ b'<b 时,结论成立。G(ab)=\sum\limits_{d|ab}F(d)G(ab/d)-\sum\limits_{d|ab,d≠1}F(d)G(ab/d) 由
e=G*F ,得\sum\limits_{d|ab}F(d)G(ab/d)=e(ab) ,由于ab>1 所以e(ab)=0 。=-\sum\limits_{d|ab,d≠1}F(d)G(ab/d) =-\sum\limits_{i|a,j|b,ij≠1}F(ij)G(ab/ij) 根据所有的
a'<a;\ b'<b 时,积性成立得=-\sum\limits_{i|a,j|b,ij≠1}F(i)G(j)F(a/i)G(b/j) =-\sum\limits_{i|a,j|b,ij≠1}F(i)G(a/i)F(j)G(b/j) 下面拆分求和号(乘法分配律),这一步有点难。
=-\sum\limits_{i|a}F(i)G(a/i)\sum\limits_{j|b,ij≠1}F(j)G(b/j) 要求
ij≠1 ,即排除i=j=1 的情况。=F(1)F(1)G(a)G(b)-\sum\limits_{i|a}F(i)G(a/i)\sum\limits_{j|b}F(j)G(b/j) 由
e=G*F ,得\sum\limits_{i|a}F(i)G(a/i)\sum\limits_{j|b}F(j)G(b/j)=e(a)e(b)=0 得=F(1)F(1)G(a)G(b) 由于积性,根据上文
F(1)=1 ,得
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
2.莫反公式的推导:
——推导了
有两个单变量函数 F 与 f 。
设函数 F 与 f 有如下关系:
理解了上述狄利克雷卷积的定义后,不难发现。
我们只要想办法求出
大佬Möbius把
问题在于这个函数里面是什么呢?
这里有个小技巧,研究一个积性函数,先研究其在质数的幂时的表现。
比如说
k=0时,显然有
k=1时,由于
注意到p为素数,得到
因为
k>1时,由于
注意到p为素数,得到
所有的
代入
考虑上式k=2的情况,得到
考虑上式k=3的情况,得到
……,得到k=任意更大的数时
利用数学归纳法,证明了k>1时,
大家缓口气哦,
由于上文积性函数性质2:
因为k>1时,
可以想象到当某一个
不然的话所有的k=1。
可以想象到
mu 函数的
现在来介绍莫比乌斯函数是怎么用来反演的。
-
反演公式:
- 嵌入式莫比乌斯反演:
由
\mu*I=e 得\sum\limits_{d|n}\mu(d)=[n=1] 注意到
[n|m][n/m=1]=[n=m] 则有
[n|m]\sum\limits_{d|(n/m)}\mu(d)=[n=m] 初学者可以只掌握这一种。
- 约数的莫比乌斯反演:
若:
\large{F(n)=∑\limits_{d|n}f(d)} 则:
\large{f(n)=∑\limits_{d|n}μ(d)F(n/d)} 又作
f(n)=∑\limits_{d|n}μ(n/d)F(d) (和上面那个式子等价)根据
\mu=I^{-1} 有f*I=F\rightarrow f=F*\mu 。如果你们喜欢和式的话:证明2
- 倍数的莫比乌斯反演:
若:
\large{F(n)=∑\limits_{n|d}f(d)} 则:
\large{f(n)=∑\limits_{n|d}μ(d/n)F(d)} (这个比较特殊,并不是狄利克雷卷积的形式)
又作
F(n)=\sum\limits_{k}^∞f(kn) 与f(n)=\sum\limits_{k}^∞\mu(k)F(kn) \color{blue}\text{证明:} ∑\limits_{n|d}μ(n/d)F(d) 将
F(n) 的定义代入。=∑\limits_{n|d}μ(n/d)\sum\limits_{d|t}f(t) =∑\limits_{k}^{∞}μ(k)\sum\limits_{nk|t}f(t) 第一个求和号对k没有限制,我们交换求和号,考虑枚举t,看有什么对应的k:
=∑\limits_{t}f(t)\sum\limits_{nk|t}\mu(k)=∑\limits_{t}f(t)\sum\limits_{k|(t/n),n|t}\mu(k) 根据嵌入式反演能得到
\sum\limits_{k|(t/n),n|t}\mu(k)=[t=n]
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
3.知识储备:
- 分解质因数求法,单个mu(n)
直接按照定义,分解因数暴力求。
代码被和谐
//Time:O(sqrt(n))
- 线性筛法,mu(1~n) (正宗)
利用
想深入学习线性筛法的看这里。
#include<iostream>
#include<cstring>
#define MaxNum 10000100
using namespace std;
bool e[MaxNum];
int p[MaxNum],tn,mu[MaxNum],n;
void Mobius()
{
e[1]=1;mu[1]=1;
for (int i=2;i<=n;i++){
if (!e[i]){p[++tn]=i;mu[i]=-1;}
for (int j=1;j<=tn;j++){
if (p[j]*i>n)break;
mu[p[j]*i]=i%p[j]==0 ? 0 : -mu[i];
e[p[j]*i]=1;
if (i%p[j]==0)break;
}
}
}
int main()
{
cin>>n;
Mobius();
for(int i=1;i<=n;++i)
printf("%d: %d\n",i,mu[i]);
return 0;
}//Time:O(n)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
4.第一道题:整除分块
(除了特殊说明,除法均为整除)
讲了那么多,那么问题来了:我们问什么要学莫比乌斯反演?这有什么用呢?
来看一道题目。
让你求
大力两重循环+欧几里得
悄悄告诉你,正解
-
即$[d|(i,j)]=[d|i][d|j]$, 人话就是 : 即是$i$的约数又是$j$的约数的数是$i,j$的公约数。
则有
观察后面的
而
就得到
这样子就可以
看到和式后面的
问题是还乘了个
(如果这句听不懂,把入门小记再看一遍)
即
Code:
#include<iostream>
#define MaxNum 100100
using namespace std;
int p[MaxNum/8],tn,mu[MaxNum],n,m;
bool e[MaxNum];
long long calc(int n,int m)
{
long long ans=0;
for (int l=1,r=0;l<=min(n,m);l=r+1){
r=min(n/(n/l),m/(m/l));// 分段
ans+=1ll*(mu[r]-mu[l-1])*(n/l)*(m/l);
}return ans;
}
int main()
{
e[1]=1;mu[1]=1;
for (int i=2;i<=MaxNum;i++){
if (!e[i]){p[++tn]=i;mu[i]=-1;}
for (int j=1;j<=tn;j++){
if (p[j]*i>MaxNum)break;
mu[p[j]*i]=i%p[j]==0 ? 0 : -mu[i];
e[p[j]*i]=1;
if (i%p[j]==0)break;
}
}for (int i=2;i<=MaxNum;i++)mu[i]+=mu[i-1];
//筛法弄出mu前缀和,参看前文
cin>>n>>m;
cout<<calc(n,m)<<endl;
return 0;
}
后面你会看到,莫比乌斯反演不分块复杂度就是一堆垃圾。
分块是肯定要分块的,这辈子都要分块的
恭喜你,已经在莫比乌斯反演的路上迈出了第一步!
附送可以利用上述代码AC的练手题Link1+Link2
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
5.技巧进阶:莫反常用结论以及练习
莫比乌斯反演是极具技巧性的。
主要的思维难度就建立在反演和分块两部分。
看题吧。
第一题P2568 GCD
求
在上一题中,我们解决了求
我们尝试求
- 注意到,
[(i,j)=d] 蕴含d|i 且d|j .
-
\color{blue}\text{技巧:}$ 既然$i,j$都是$d$的倍数,我们不妨把$i,j$都除以$d 此时,后面原有的
i 需要还原成id ,原有的j 要还原成jd
-
[(id,jd)=d]=[d(i,j)=d]=[(i,j)=1]
对于每个素数求解的复杂度是
将素数视为平均分布,可估计额
接下来需要一点微积分知识 :
总的复杂度就是
对于不会计算复杂度的同学,可以暴力统计一下式子的值,看看能不能过就好了。
Code:
#include<iostream>
#include<cstdio>
#define MaxNum 10000010
using namespace std;
long long p[MaxNum/10],tn,mu[MaxNum+50],anss,ttn,n,aaa;
bool e[MaxNum+50];
void getmu()
{
e[1]=1;mu[1]=1;
for (long long i=2;i<=MaxNum;i++){
if (!e[i])mu[p[++tn]=i]=-1;
for (int j=1;j<=tn&&i*p[j]<MaxNum;j++){
e[i*p[j]]=1;
mu[i*p[j]]=i%p[j]? -mu[i] : 0;
if (!i%p[j])break;
}
}
}
//sum(1,N/n)sum(1,N/n)[gcd(i,j)==1]
long long gg(int n){
long long ans=0;
for (int l=1,r=0;l<=n;l=r+1){
r=n/(n/l); // 分段
ans+=1ll*(mu[r]-mu[l-1])*(n/l)*(n/l);
}return ans;
}
int main()
{
getmu();
for (int i=2;i<=MaxNum;i++)mu[i]+=mu[i-1];
cin>>n;
long long ans=0;
for (int i=2;i<=n;i++)
if(!e[i])ans+=gg(n/i);
cout<<ans;
return 0;
}
P2257 YY的GCD
貌似和上一题相同呢?发现
上一题的
我们将上面的最终式子整理:
明显的,
我们发现只有
得到
前面的
至于后面的
怎么弄出前缀和,就是考验数论口胡基本功的时候啦,这里也介绍一下。
设
先线筛出
这样的话对于一个素数
复杂度和埃氏筛相同,为
每个询问
亮出代码:
#include<algorithm>
#include<cstdio>
using namespace std;
int t,n,m,tn,p[1000500],mu[10000500],g[10000500];
bool e[10000500];
void getmu()
{
e[1]=1;mu[1]=1;
for (int i=2;i<=10000100;i++){
if (!e[i]){
p[++tn]=i;
mu[i]=-1;
}for (int j=1;j<=tn;j++){
if (1ll*i*p[j]>10000100)break;
e[i*p[j]]=1;
mu[i*p[j]]=i%p[j] ? -mu[i] : 0;
if (!i%p[j])break;
}
}//线筛出mu
}
long long calc(int n,int m)
{
long long ans=0;
int l=1,r;
for (;l<=min(n,m);l=r+1){
r=min(n/(n/l),m/(m/l));//整除分块
ans+=1ll*(n/l)*(m/l)*(g[r]-g[l-1]);
}return ans;
}
int main()
{
scanf("%d",&t);
getmu();
for (int i=1;i<=tn;i++)
for (int j=p[i];j<=10000100;j+=p[i])
g[j]+=mu[j/p[i]];
for (int i=2;i<=10000100;i++)g[i]+=g[i-1];
//预处理前缀和
while(t--){
scanf("%d%d",&n,&m);
printf("%lld\n",calc(n,m));
}return 0;}
P5221 Product & My Sol
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
6.探索新知:其他数论函数
想必经过上面的学习,大家对
设
得
即
由于互质,
因为
我们还要证明所有的
首先,这些二元组的大小为
前置芝士 :
-
采用反证法,假如有
am+bn=cm+dn\pmod {nm} 我们将两边对
m 取模得到bn=dn\pmod {m} ,由引理得b=d ,同理有a=c ,矛盾。
那么
于是上式等价于
如何求积性函数的前n个值呢?
线性筛!
对于线性筛,我们要解决
则
原命题明显成立。
Code:
bitset<MaxN> e;
int p[MaxN/10],tn;
long long ans,phi[MaxN];
void getphi()
{
phi[1]=1;
for (int i=2;i<=n;i++){
if (!e[i]){
p[++tn]=i;
phi[i]=i-1;
}for (int j=1,t;j<=tn&&(t=i*p[j])<=n;j++){
e[t]=1;
phi[t]=phi[i]*(i%p[j]?p[j]-1:p[j]);
//此处注意
if (i%p[j]==0)break;
}
}
}
-
代码里"此处注意":
如果
(i,p)=1 ,则\varphi(ip)=\varphi(i)*\varphi(p)=(p-1)\varphi(i) 如果
p|i ,根据上文的定理,\varphi(ip)=\varphi(i)*p -
附: 有些时候我们需要求出单个的
\varphi(n) ,如果按照定义大力求,至少得O(n) 次\gcd 。我们把
n 分解得p_1^{k_1}*p_2^{k_2}*...*p_m^{k_m} 那么
\varphi(n)=\varphi(p_1^{k_1})*\varphi(p_2^{k_2})*...*\varphi(p_m^{k_m})=\dfrac{(p_1-1)p_1^k}{p_1}*\dfrac{(p_2-1)p_2^k}{p_2}*...\dfrac{(p_m-1)p_m^k}{p_m} =(p_1^kp_2^{k_2}...p_m^{k_m})*\left(\dfrac{p_1-1}{p_1}*\dfrac{p_2-1}{p_2}*...*\dfrac{p_m-1}{p_m}\right)=n\left(\dfrac{p_1-1}{p_1}*\dfrac{p_2-1}{p_2}*...*\dfrac{p_m-1}{p_m}\right) \color{blue}\text{性质:}$ $\varphi(n=p_1^kp_2^{k_2}...p_m^{k_m})=n\left(\dfrac{p_1-1}{p_1}*\dfrac{p_2-1}{p_2}*...*\dfrac{p_m-1}{p_m}\right) 根据上式,把
n 质因数分解即可求出\varphi(n) ,复杂度O(\sqrt{n}) Code:
int phi(int n)
{
int ans=n;
for (int i=2;i*i<=n;i++)
if (n%i==0){
ans=ans/i*(i-1);
//根据上文式子计算
while(n%i==0)n/=i;
}
if (n>1)ans=ans/n*(n-1);
//如果分解不尽,那么肯定还剩一个素数
return ans;
}
我们来看看
题目
相信你能直接想到一个莫比乌斯反演的做法:
-
设
f(n) 是\sum\limits_{i=1}^n\sum\limits_{j=1}^n[(i,j)=1] 考虑枚举gcd那么答案是
\sum\limits_{d=1}^nd\sum\limits_{i=1}^n\sum\limits_{j=1}^n[(i,j)=d] 后面的
\sum\limits_{i=1}^n\sum\limits_{j=1}^n[(i,j)=d]=\sum\limits_{i=1}^{(n/d)}\sum\limits_{j=1}^{(n/d)}[(i,j)=1]=f(n/d) 答案化为
\sum\limits_{d=1}^nd*f(n/d) 。对
f(n) 反演,……即为∑\limits_{d=1}^{n}μ(d)(n/d)^2 ,用整除分块求一次f 时间复杂度O(\sqrt{n}) 按
\sum\limits_{d=1}^nd*f(n/d) 计算即可O(n\sqrt{n}) 。考虑对
f(n/d) 的n/d 整除分块,即可O(n)-O(n^{3/4}) (方法参照上文,证明需用积分)。
其实利用
设
我们知道
我们把
即
现在,每个数只能统计比自己小的互质产生贡献,也就是,只统计了数对
我们考虑
(想像一个邻接矩阵)
乘以
我们设
答案变为
然后使用线性筛就得到
Code:
#include<cstdio>
#include<bitset>
#define MaxN 10000500
using namespace std;
int n;
bitset<MaxN> e;
int p[MaxN/10],tn;
long long ans,phi[MaxN];
void getphi()
{
phi[1]=1;
for (int i=2;i<=n;i++){
if (!e[i]){
p[++tn]=i;
phi[i]=i-1;
}for (int j=1,t;j<=tn&&(t=i*p[j])<=n;j++){
e[t]=1;
phi[t]=phi[i]*(i%p[j]?p[j]-1:p[j]);
if (i%p[j]==0)break;
}
}
}
int main()
{
scanf("%d",&n);
getphi();
for (int i=1;i<=n;i++)phi[i]+=phi[i-1];
for (int i=1;i<=n;i++)ans+=(phi[n/i]*2-1)*i;
printf("%lld",ans);
}
这个解法相对莫比乌斯反演有什么好处呢?
代码短啊!
如果多组询问的话,就可以使用数论分块
重申经典结论:
既然欧拉函数这么好用,为啥还要反演呢?
求
这么说太过于人类智慧了,下面,我们考虑使用狄利克雷卷积来系统分析。
2)一些狄利克雷卷积结论:
-
id=I*\varphi
即
这个东西是比较重要的,相应地,证明也很长。
我们知道
然后根据积性和唯一分解定理,把结论扩展到全体正整数。
根据
证毕。
根据
即
这个结论极其有用,比如说我们求
利用莫比乌斯反演,变形为
设
可以交换求和号,得到
发现中间的
(这个式子是可以整除分块的)
这样就可以解决上文留下的问题了。
3)除数函数相关
-
σ=id*I
把
有
这个容易,假如
否则按照积性分解即可。
- 除数函数
d(n)
有
比较难,只会从右边推到左边。
把
显然,每个质因子是独立的,我们考虑
大眼观察可得只有
有
默认
考虑到