莫比乌斯反演——从入门到入土
其实我也不会的,毕竟我是个蒟蒻
我们先来学习一些前置技能
本文保证大多数图片为作者制作
莫比乌斯函数
莫比乌斯函数,数论函数,由德国数学家和天文学家莫比乌斯(August Ferdinand Möbius ,1790–1868)提出。梅滕斯(Mertens)首先使用μ(n)作为莫比乌斯函数的记号。而据说,高斯(Gauss)比莫比乌斯早三十年就曾考虑过这个函数。莫比乌斯函数在数论中有着广泛应用。
那它究竟是什么呢?
我们可以来看一看它的定义
举个栗子
μ(2)=−1,μ(3)=−1,μ(6)=1,μ(9)=0
μ
(
2
)
=
−
1
,
μ
(
3
)
=
−
1
,
μ
(
6
)
=
1
,
μ
(
9
)
=
0
其中6=2×3
其
中
6
=
2
×
3
,
9=32
9
=
3
2
性质
1.(n为任意正整数)
2.(n为任意正整数)
下面给出性质2的证明
3.mobius函数为积性函数
如何快速获得mobius函数值
我们需要前前前前前前前前前前前前前前前前前前前前前前前前前前前前前前前前置技能—-线性筛(不会的读者可以先去其他文章学习)
我们需要再大力改一改就好了
inline void get_mobius()
{
mobius[1]=1;
for(int i=2;i<=MAXN;i++)
{
if(!not_prime[i])
{
prime[++prime_len]=i;
mobius[i]=-1;//质数的mobius函数值为-1是显然的
}
for(int j=1;j<=prime_len&&i*prime[j]<=MAXN;j++)
{
not_prime[i*prime[j]]=true;
if(i%prime[j]==0)
{
mobius[i*prime[j]]=0;//存在两个相同的质因子prime[j],所以值为0
break;
}
mobius[i*prime[j]]=-mobius[i];//添加一个质因子,指数+1,就是在原来基础上乘-1
}
}
}
欧拉函数
在数论,对正整数n,欧拉函数是小于或等于n的数中与n互质的数的数目。此函数以其首名研究者欧拉命名,它又称为Euler’s totient function、φ函数、欧拉商数等。 例如φ(8)=4,因为1,3,5,7均和8互质。 从欧拉函数引伸出来在环论方面的事实和拉格朗日定理构成了欧拉定理的证明。
这个东西不是本文主要讲解内容,但因为莫比乌斯函数的性质一,可以将莫比乌斯函数化为欧拉函数,从而快速的解决问题
下面简单介绍两个性质
1.欧拉定理(a,p互质)
2.,然而我并不会证,读者可以到其他文章学习
如何快速获取欧拉函数
inline void getphi()
{
phi[1]=1;//规定1的欧拉函数为1
for(int i=2;i<=MAXN;i++)
{
if(!not_prime[i])
{
prime[++prime_len]=i;
phi[i]=i-1;//质数只有它本身不与自己互质
}
for(int j=1;j<=prime_len&&prime[j]*i<=MAXN;j++)
{
not_prime[i*prime[j]]=true;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];//i中含有prime[j],再次去掉prime[j]的倍数会重复减数
break;
}
phi[i*prime[j]]=phi[i]*(prime[j]-1);//i中不含prime[j],去掉prime[j]的倍数
}
}
}
好了,我们大概已经学会了前置技能了
莫比乌斯反演定理
设有函数F(x),f(x),满足这样的性质
现在我们给出
F1,F2,F3,......,Fn
F
1
,
F
2
,
F
3
,
.
.
.
.
.
.
,
F
n
,想要求
f1
f
1
我们想通过F(i)的值来得到f(1),该怎么做呢?
第一种做法就是大力Gauss消元,但对于
n≤107
n
≤
10
7
的数据范围来讲,Gauss消元的时间复杂度为
n×(1+12+13+......+1n)
n
×
(
1
+
1
2
+
1
3
+
.
.
.
.
.
.
+
1
n
)
≈n×∫n11x
≈
n
×
∫
1
n
1
x
=n×(lnn−ln1)
=
n
×
(
ln
n
−
ln
1
)
=nlnn
=
n
ln
n
也就是说Gauss消元的的复杂度近似于
O(nlnn)
O
(
n
ln
n
)
带入
n=107
n
=
10
7
具体计算次数就是
138155105
138155105
,大概为
1.3×108
1.3
×
10
8
,显然可以得到比较多的分数,但一般是60分左右,那怎么才能A掉这道题呢?
下面我们来介绍莫比乌斯反演定理
1.
2.
利用这个定理,我们再带入n=1,就可以
O(n)
O
(
n
)
解决这个问题了
下面给出莫比乌斯反演的证明
第一个形式的证明:
第二个形式的证明:
这就很妙了
例题显然是莫比乌斯反演定理的第二个形式
带入n=1
可知
f1=∑i=1nμ(i)ai
f
1
=
∑
i
=
1
n
μ
(
i
)
a
i
我们就可以
O(n)
O
(
n
)
求解啦
是不是很妙呢
mobius 反演例题
T1
题目传送门
这可能是一道非常经典的mobius反演入门题
这种题显然不能很大力地暴力枚举,所以我们考虑其他方法降低复杂度
本文主要介绍莫比乌斯反演,所以正解显然是要用到莫比乌斯反演定理
本文的读者应该是来入门的,初次接触很难想象到莫比乌斯反演定理是如何运用的
此题权当莫比乌斯反演的入门开发思路题
本题是在求
∑i=1n∑j=1m[gcd(i,j)==d]
∑
i
=
1
n
∑
j
=
1
m
[
g
c
d
(
i
,
j
)
==
d
]
考虑大力化简
这个式子就可以帮我们大力降低时间复杂度,将复杂度变为
O(min(n,m)d)
O
(
m
i
n
(
n
,
m
)
d
)
优化去掉了一个n,变成了线性时间
但是还是无法完成题目里的查询
50000∗50000=2.5×109
50000
∗
50000
=
2.5
×
10
9
远大于1s
怎么办呢?
我们考虑枚举
kd
k
d
的值,显然只有
min(n,m)
m
i
n
(
n
,
m
)
种取值
设
D=kd
D
=
k
d
f(d)=∑D=1min(n,m)μ(D)F(Dd)
f
(
d
)
=
∑
D
=
1
m
i
n
(
n
,
m
)
μ
(
D
)
F
(
D
d
)
这种形式便于枚举
因为它可以利用前缀和进行分块
每次去枚举
F(Dd)
F
(
D
d
)
中两个式子的值
⌊nDd⌋和⌊mDd⌋
⌊
n
D
d
⌋
和
⌊
m
D
d
⌋
显然对于任意正整数
n
n
,其因子个数不超过个
于是
⌊nDd⌋和⌊mDd⌋
⌊
n
D
d
⌋
和
⌊
m
D
d
⌋
它们的取值都各只有根号级别种
有很多组区间,满足对于一段区间它们的
F(Dd)
F
(
D
d
)
值是一样的
你只需要大力前缀和就可以搞了
复杂度
O(n+qn−−√)
O
(
n
+
q
n
)
附上AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 50010
using namespace std;
inline void read(int &x)
{
int s=0,w=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')w=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
s=(s<<3)+(s<<1)+c-'0';
c=getchar();
}
x=s*w;
}
int mobius[MAXN],prime[MAXN],prime_len,sum[MAXN];
bool not_prime[MAXN];
inline void getmobius()
{
mobius[1]=1;
sum[1]=1;
for(int i=2;i<=MAXN;i++)
{
if(!not_prime[i])
{
prime[++prime_len]=i;
mobius[i]=-1;
}
for(int j=1;j<=prime_len&&i*prime[j]<=MAXN;j++)
{
not_prime[i*prime[j]]=1;
if(i%prime[j]==0)
{
mobius[i*prime[j]]=0;
break;
}
mobius[i*prime[j]]=-mobius[i];
}
sum[i]=sum[i-1]+mobius[i];
}
}
int T;
int main()
{
getmobius();
read(T);
while(T--)
{
int a,b,d;
read(a),read(b),read(d);
long long ans=0;
if(a>b)swap(a,b);
a/=d;
b/=d;
for(int i=1;i<=a;)
{
int j=min(a/(a/i),b/(b/i));
ans+=(long long)(sum[j]-sum[i-1])*(a/i)*(b/i);
i=j+1;
}
printf("%lld\n",ans);
}
}
T2:
此题并不难,和上一题是一样的,但也有一些区别,题目给出了上界和下界
problem b 题目传送门
这个东西就很好搞了,大力容斥即可
时间复杂度
O(n+4qn−−√)≈O(n+qn−−√)
O
(
n
+
4
q
n
)
≈
O
(
n
+
q
n
)
紫色区域+黄色区域-蓝色区域-灰色区域即为答案
附上AC代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 50010
using namespace std;
inline void read(int &x)
{
int s=0,w=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-')w=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
s=(s<<3)+(s<<1)+c-'0';
c=getchar();
}
x=s*w;
}
int mobius[MAXN],prime[MAXN],prime_len,sum[MAXN];
bool not_prime[MAXN];
inline void getmobius()
{
mobius[1]=1;
sum[1]=1;
for(int i=2;i<=MAXN-1;i++)
{
if(!not_prime[i])
{
prime[++prime_len]=i;
mobius[i]=-1;
}
for(int j=1;j<=prime_len&&i*prime[j]<=MAXN;j++)
{
not_prime[i*prime[j]]=1;
if(i%prime[j]==0)
{
mobius[i*prime[j]]=0;
break;
}
mobius[i*prime[j]]=-mobius[i];
}
sum[i]=sum[i-1]+mobius[i];
}
}
inline long long solve(int a,int b,int k)
{
if(a>b)swap(a,b);
a/=k;
b/=k;
long long ans=0;
for(int i=1;i<=a;)
{
int j=min(a/(a/i),b/(b/i));
ans+=(long long)(sum[j]-sum[i-1])*(a/i)*(b/i);
i=j+1;
}
return ans;
}
int T;
int main()
{
getmobius();
read(T);
while(T--)
{
int a,b,c,d,k;
read(a),read(b),read(c),read(d),read(k);
printf("%lld\n",solve(b,d,k)+solve(a-1,c-1,k)-solve(b,c-1,k)-solve(a-1,d,k));
}
}
不定时更新未完待续