Dyd's Blog

He who has a strong enough why can bear almost any how.

莫比乌斯反演

数学的恶魔

莫比乌斯反演

前置知识:数论分块

基本内容

数论分块可以快速计算一些含有除法向下取整的和式(即形如 i=1nf(i)g(ni) 的式子),只要可以在 O(1) 内计算 f(r)f(l) 或已经预处理出 f 的前缀和时,可以用数论分块在 O(n) 的时间内计算答案。

它主要利用富比尼定理(Fubini’s theorem)听起来很厉害但其实没啥):我们注意到,有很多的 i 它们的 ni 是相等的,这启发我们分段打包计算。

举个例子,如果我们要求 i=1nni ,我们发现很多 i 都对应同一个 ni ,它们呈块状分布,在打个表仔细观察(证明),发现对于每一个块,它的最后一个数是 n/(n/i)c++ 自带下取整,一般我们称其这个式子的值为 g(i) ),如对于 i=11010i ,分成: (1)(2)(3)(4,5)(6,7,8,9,10) ,其中 1=10/(10/1),2=10/(10/2),3=10/(10/3),5=10/(10/5),10=10/(10/10) ,于是,求解上述问题的代码如下(常用):

UVA11526 H(n)

1
2
3
4
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans+=(r-l+1)*(n/l);
}

类似的,我们可以用 O(n) 计算 g(ni) 那么只要 f 好求我们就能算了!

一般而言,我们可以统计一个前缀和(积),因为每当我们使用分块跳过一个区间的时候,其所对应的函数值也跳过了一个区间。所以此时,就需要乘上那一个区间的函数值(可以自己举个例子理解一下)。

但要是不一般(比如 O(n) 的前缀和都会TLE),那……去死吧!(dalao可以用杜教筛试一试)

引理一

a,b,cZ,abc=abc

证明:
ab=ab+r(0r<1)abc=1c(ab+r)=abc+rc=abc

引理二

nN+,|nd|dN+dn|2n

其中 |V| 表示集合 V 的元素个数

该引理是时间复杂度的保证

证明:

对于 dn ,由于 d 最多有 n 种不同取值, nd 也只有 n 种取值

对于 d>n ,则 ndn ,也只有 n 种取值

前置知识:积性函数

若函数 f(x) 为积性函数,则有 gcd(a,b)=1,f(ab)=f(a)f(b)

可以证明以下函数全是积性函数:

  • φ(n) -欧拉函数
  • μ(n) -莫比乌斯函数
  • gcd(n,k) -最大公因子,当k固定的情况
  • d(n) -正因子数目
  • σ(n) -所有正因子之和
  • σk(n) -因子函数, n 的所有正因子的 k 次幂之和,当中 k 可为任何复数
  • 1(n) -不变的函数,定义为 1(n)=1 (完全积性)
  • Id(n) -单位函数,定义为 Id(n)=n (完全积性)
  • Idk(n) -幂函数,对于任何复数、实数 k ,定义为 Idk(n)=nk (完全积性)
  • ε(n) -定义为:若 n=1ε(n)=1 ;若 n>1ε(n)=0 。别称为“对于狄利克雷卷积的乘法单位”(完全积性)
  • λ(n)刘维尔函数,关于能整除 n 的质因子的数目
  • γ(n) ,定义为 γ(n)=(1)ω(n),在此加性函数 ω(n) 是不同能整除n的质数的数目
  • 另外,所有狄利克雷特征均是完全积性的

积性函数一般可以用线性筛筛得

前置知识:狄利克雷卷积

定义

狄利克雷( Dirichlet )卷积定义为:

对于两个数论函数 f,g ,有:
(fg)(n)=d|nf(d)g(nd)

性质

狄利克雷卷积满足以下运算律:

  • 交换律: (fg)=(gf)
  • 结合律: ((fg)h)=(f(gh))
  • 分配律: (f(g+h))=((fg)+(fh))
  • (fε)=f ,其中 ε 为狄利克雷卷积的单位元

for example:

  • ε=(μ1)ε(n)=d|nμ(d)1(nd)=d|nμ(d)
  • d=(11)d(n)=d|n1(d)1(nd)=d|n1
  • Id=(φ1)Id(n)=n=d|nφ(n)
  • φ=(μId)φ(n)=d|ndμ(nd)

求法

对于卷积的第 n 项可以直接枚举约数,在 O(n) 的时间内计算

但是对于狄利克雷卷积的前 n
h[i]=d|if[d]g[i/d]
如果一项一项算的话就需要 O(nn) ,但实际上可以优化

x=d,y=id 分别枚举 x,y 对于 h[xy]+=f[x]g[y] 即可。
时间复杂度 O(nlogn)

1
2
3
for(int i=1;i<=n;++i) h[i]=0;
for(int i=1;i<=n;++i)
for(int j=1;j*i<=n;++j) h[i*j]=(h[i*j]+f[i]*g[j]%P)%P;

对于 k 次卷积,还可以快速幂优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct HanShu{
int f[N];
void inint(){
for(int i=1;i<=n;++i) f[i]=0;
}
HanShu operator*(const HanShu _t){
HanShu h;
h.inint();
for(int i=1;i<=n;++i)
for(int j=1;j*i<=n;++j) h.f[i*j]=(h.f[i*j]+f[i]*_t.f[j]%P)%P;
return h;
}
void operator=(const HanShu _t){
for(int i=1;i<=n;++i) f[i]=_t.f[i];
}
};
HanShu H_qpow(HanShu x,int y){
HanShu res;
int _t=0;
while(y){
if(y&1){
++_t;
if(_t==1) res=x;
else res=res*x;
}
x=x*x;
y>>=1;
}
return res;
}

前置知识:莫比乌斯函数

定义

设正整数 N 分解质因数为 N=p1c1p2c2pmcm ,定义函数
μ(N)={0i[1,m],ci>11m0(mod 2),i[1,m],ci=11m1(mod 2),i[1,m],ci=1
我们称 μ 为莫比乌斯( Mo¨bius )函数。

性质和结论

  • 莫比乌斯函数是积性函数

  • d|nμ(d)=[n==1] ,其中 [p] 表示 p 为真时取 1 其他时候取 0 ,该式也可表示为 d|nμ(d)=ε(n) ,也就是 μ1=ε

    证明:

    n=i=1kpici,n=i=1kpi

    那么 d|nμ(d)=d|nμ(d)=i=0kCki(1)i

    又因为二项式定理: (a+b)k=i=0kCkiaibki

    有: i=0kCki(1)i=i=0kCki(1)i1ki=((1)+1)k

    当且仅当 k=0 ,即 n=1 时原式为 1 ,其余时候都为 0

  • [gcd(i,j)==1]=d|gcd(i,j)μ(d) 该结论在反演中十分常用,正确性显然

筛法

若只求一项莫比乌斯函数,分解质因数即可;

若要求多项可以在线性筛素数时一并求出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int primes[N],mu[N],cnt=0;
bool v[N];
mu[1]=1; //1特判
for(int i=2;i<=n;++i){
if(!v[i]) primes[++cnt]=i,mu[i]=-1; //质数
for(int j=1;j<=cnt&&primes[j]*i<=n;++j){
v[primes[j]*i]=true;
if(i%primes[j]==0){ //若有一个质因数指数大于1
mu[i*primes[j]]=0;
break;
}
mu[i*primes[j]]=-mu[i];
}
}

正题:莫比乌斯反演

定理

F(n)f(n) 是定义在 N 上的函数,且有
F(n)=d|nf(d)
则:
f(n)=d|nμ(d)F(nd)
我们称该定理为莫比乌斯反演定理(原来是用 f 表示 F ,现在反过来了)。

证明如下:
(1)d|nμ(d)F(nd)=d|nμ(d)i|ndf(i)(2)=i|nf(i)d|niμ(d)(3)=i|nf(i)[ni==1](4)=f(n)
当然,不难发现,狄利克雷卷积也可以证明:

(5)F(n)=d|nf(d)(6)F=f1(7)Fμ=f1μ(8)μ1=ε(9)Fμ=f(1μ)=f(10)f=μF

然而,在莫比乌斯反演中更常用的是这个式子:

F(n)f(n) 满足:
F(n)=n|df(d)
则有:
f(n)=n|dμ(dn)F(d)
其实就是枚举约数变成了枚举倍数,若用卷积证明,过程都一样,下面用定义证明:
(11)n|dμ(dn)F(d)=n|dμ(dn)d|if(i)(12)不妨设d=dn(13)原式=n|if(i)d|inμ(d)(14)=n|if(i)[in==1](15)=f(n)
以上就是莫比乌斯反演的常用形式(要记住)

例题一

Problem b

分析:

若我们将数对 (x,y) 对应到坐标系内,其实是要求在左下角为 (a,c) 右上角为 (b,d) 的矩形中有多少个点 (x,y) 满足 gcd(x,y)==k

由矩形考虑二维前缀和,设 S(x,y) 表示以 (x,y) 为右上角, (0,0) 为左下角的矩形中满足要求的点的个数,则 Ans=S(b,d)S(a1,d)S(b,c1)+S(a1,c1)

考虑用莫比乌斯反演,我们需要定义 F,f 且让 F(n)=n|df(d) (即满足反演条件),要注意的是, F 应该是比较好计算的函数(至少要比 f 好计算,不然你反演用 F 表示 f 干嘛),还有就是 f 要与答案有关系(不然求出 f 干嘛)

那么,不妨令 f(n)=x=1Ny=1M[gcd(x,y)==n] 则我们要求的 S(N,M) 就是 f(k)

又因为 F(n)=n|df(d) ,故定义 F(n)=x=1Ny=1M[n|gcd(x,y)]

反演得: f(n)=n|dμ(dn)F(d)

又因为由 F 的定义, n|gcd(x,y)n|xn|y ,故 F(d)=NdMd

所以 f(n)=n|dμ(dn)NdMd

枚举倍数比枚举因数要简单,所以我们令 d=dn,N=Nn,M=Mn ,原式化为 f(n)=dμ(d)NdMd 预处理 μ 的前缀和后用数论分块可以求

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=50000+5;
int pri[N],cnt,mu[N];
bool v[N];
LL sum[N];
void pred(){
mu[1]=1;
for(int i=2;i<N;++i){
if(!v[i]) pri[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt&&pri[j]*i<N;++j){
v[pri[j]*i]=true;
if(i%pri[j]==0){
mu[i*pri[j]]=0;
break;
}
mu[i*pri[j]]=-mu[i];
}
}
sum[0]=0;
for(int i=1;i<N;++i) sum[i]=sum[i-1]+mu[i];
}
int g(int k,int x){ //得到(k/x)中x所在块的右端点
return k/(k/x);
}
LL S(int x,int y,int k){
LL res=0;
x=x/k,y=y/k;
int _t=min(x,y);
for(int l=1,r;l<=_t;l=r+1){
r=min(_t,min(g(x,l),g(y,l))); //每次在两个取整中找小的,注意不能超过_t
res+=(sum[r]-sum[l-1])*(x/l)*(y/l);
}
return res;
}
int main(){
int n;
int a,b,c,d,k;
pred();
scanf("%d",&n);
while(n--){
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
printf("%lld\n",S(b,d,k)-S(a-1,d,k)-S(b,c-1,k)+S(a-1,c-1,k));
}
return 0;
}

例题二

约数个数和

分析:

先看原题给我们的函数,由于 d 是积性函数(忘了的上去翻),所以有 d(ij)=x|iy|j[gcd(x,y)==1]呃呃呃,鬼想的到啊!

简单证明:

i=p1α1p2α2pkαk,j=p1β1p2β2pkβk ,则 ij=i=p1α1+β1p2α2+β2pkαk+βk ,由约数个数定理 d(ij)=(α1+β1+1)(α2+β2+1)(αk+βk+1)

再看等式右边, x,y 的因数也一定是在 p1,p2,,pk 中,对于质因子 p1 ,若 xp1t,(t0) ,则有意义的 y 一定不含 p1 ,此时 tα1 种取值,同理,若 yp1t,(t0)β1 种取值,加上一种 x,y 都不含 p1 ,共 α1+β1+1 种有意义的取值,类似的,讨论其它质因数,乘法原理得左式等于右式

QED

我们发现 Ans=i=1Nj=1Mx|iy|j[gcd(x,y)==1] ,类似与上题,令: f(n)=i=1Nj=1Mx|iy|j[gcd(x,y)==n],F(n)=i=1Nj=1Mx|iy|j[n|gcd(x,y)] 那么 Ans=f(1)

F(n)=n|df(d) ,反演得 f(n)=n|dμ(dn)F(d)

f(1)=d=1Nμ(d)F(d) ,为了求出 f(1) ,我们来看 F(n) ,由于 [n|gcd(x,y)]i,j 无关,我们可以枚举 x,y 计算有多少 i,j 符合要求, F(n)=x=1Ny=1MNxMy[n|gcd(x,y)]

同上题一样,可设 x=xn,y=yn,N=Nn,M=Mn ,得 F(n)=x=1Ny=1MNxMy

调换循环顺序得 F(n)=(x=1NNx)(y=1MMy)

h(k)=i=1kki ,有 F(n)=h(N)h(M) ,故 f(1)=d=1Nμ(d)h(Nd)h(Md) ,预处理所有的 h(x) ,数论分块解决

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=50000+5;
int pri[N],cnt,mu[N];
LL sum[N],h[N];
bool v[N];
int g(int k,int x){
return k/(k/x);
}
void pred(){
mu[1]=1;
for(int i=2;i<N;++i){
if(!v[i]) pri[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt&&pri[j]*i<N;++j){
v[pri[j]*i]=true;
if(i%pri[j]==0){
mu[i*pri[j]]=0;
break;
}
mu[i*pri[j]]=-mu[i];
}
}
sum[0]=0;
for(int i=1;i<N;++i) sum[i]=sum[i-1]+mu[i];
for(int i=1;i<N;++i)
for(int l=1,r;l<=i;l=r+1){
r=min(i,g(i,l));
h[i]+=(r-l+1)*(i/l);
}
}
int main(){
pred();
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
LL ans=0;
int _t=min(n,m);
for(int l=1,r;l<=_t;l=r+1){
r=min(_t,min(g(n,l),g(m,l)));
ans+=(sum[r]-sum[l-1])*h[n/l]*h[m/l];
}
printf("%lld\n",ans);
}
return 0;
}

例题三

龙哥的问题

本题比较简单,甚至不用莫反

考虑答案:
(16)Ans=i=1ngcd(i,n)(17)=d|n(d×i=1n[gcd(i,n)==d])(18)=d|n(d×i=1n[gcd(id,nd)==1])(19)=d|nd×φ(nd)
对于每一个 n 枚举约数( int 范围内最多的一个数约数只有 1600 多个) ,再 nφ ,本题的范围可以通过

那可否继续优化呢?令 k=nd ,再继续化一下:
(20)Ans=d|nd×φ(nd)(21)=k|nnk×φ(k)(22)=k|nnk×k(11pi)(23)=nk|n(11pi)
其中 kt=pici,t ,又因为 k|n ,不妨设 n=piαi ,下面的变化比较常用,要记住:
(24)k|d(25)kt,0ci,tαi(26)k|nk=tipici,t=tipici,t(27)=(p10+p12++p1α1)(p20+p22++p2α2)

如果不理解的话可以这样想:每个括号里取一项作 pici,t 乘起来刚好对应一个 kt

那么考虑上式对答案的贡献,我们发现对于所有的 k ,若取第 i 个质因子的指数为 0 (即取 pi0 )对答案贡献为 1 ,其他情况(即 pic,c0 )对答案的贡献都是 11pi ,故答案可化为:
(28)Ans=ni(1+(11pi)+(11pi)++(11pi))(29)=ni(1+a(11pi))
分解 n 的质因子,可以在 O(n) 时间求解,但注意,本题对 long long 卡的非常死,甚至在计算 ans 时先乘再除都会炸 long long

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<bits/stdc++.h>
#define LL long long
using namespace std;

int main(){
LL n;
scanf("%lld",&n);
LL ans=n; //最初的n
for(LL i=2;i*i<=n;++i)
if(n%i==0){
LL a=0;
while(n%i==0) ++a,n/=i;
ans/=i,ans*=i+a*i-a; //一定要先除
}
if(n>1) ans/=n,ans*=n+n-1;
printf("%lld\n",ans);
return 0;
}

例题四

来看一道难题:

数字表格

这道题要点很杂很多,我们慢慢看:

首先明确
Ans=i=1nj=1mfb(gcd(i,j))
其中 fb(x) 表示Fibonacci数列第 x

由于 不好处理,考虑枚举 gcd
Ans=d=1N[fb(d)]f(d)
其中 N=min(n,m)f(d)=i=1mj=1n[gcd(i,j)==d]

发现这个 f 的定义我们非常熟悉,于是一通莫反:
(30)F(d)=i=1mj=1n[d|gcd(i,j)](31)则有F(d)=r|df(r)(32)反演得f(d)=d|rμ(rd)F(r)(33)又有d|gcd(i,j)d|i,d|j(34)F(d)=ndmd(35)带入得f(d)=d|rμ(rd)nrmr(36)w=rd,f(d)=w=1Ndμ(w)nwdmwd
此时我们发现在莫反后, f(d) 可以在 O(n) 内计算,但是,我们观察 Ans ,对于每个 df(d) 都必须重新计算(因为 fn,m,d 都有关),这样的时间负责度为 O(Tn(n+logn)) 直接爆炸好吧

必须考虑优化,但 fb 都太不好搞了, f 也不好在变形(我在这里卡了好久),既然两个都不好搞,我们不妨把它们一起来看看:
Ans=d=1N[fb(d)]w=1Ndμ(w)nwdmwd
我们发现一个关键点:每次询问只修改 n,md,w 对于每次询问来说是一样的,这启发我们把几个与 n,m 无关的项拿出来预处理:

(37)Ans=d=1N[fb(d)]w=1Ndμ(w)nwdmwd(38)=d=1N[fb(d)]w=1Ndμ(w)nwdmwd(39)p=wd,Ans=p=1N[d|pfb(d)]μ(pd)npmp(40)h(p)=d|pfb(d)μ(pd),Ans=p=1Nh(p)npmp

发现指数位置的 npmp 可以数论分块,而 h 可以提前预处理出前缀积,如此,回答询问的总时间变为 O(Tnlogn)

下面考虑如何计算 h ,由于因数不好枚举,我们改为枚举倍数:
(41)h(p)=d|pfb(d)μ(pd)(42)=i=1Nfb(pi)μ(i)(43)变形得h(ij)=i=1Nj=1Nifb(j)μ(i)
预处理时间为 O(nlogn)

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1000000+5,P=1000000000+7;
LL pri[N],cnt,mu[N],fb[N],h[N],inv_fb[N],mul[N];
bool v[N];
int g(int k,int x){
return k/(k/x);
}
LL qpow(LL x,LL y){
LL res=1;
while(y){
if(y&1) res=(res*x)%P;
x=(x*x)%P;
y>>=1;
}
return res;
}
void pred(){
mu[1]=1;
for(int i=2;i<N;++i){
if(!v[i]) pri[++cnt]=i,mu[i]=-1;
for(int j=1;j<=cnt&&pri[j]*i<N;++j){
v[pri[j]*i]=true;
if(i%pri[j]==0){
mu[i*pri[j]]=0;
break;
}
mu[i*pri[j]]=-mu[i];
}
}
fb[0]=0;
fb[1]=1;
inv_fb[1]=1; //特殊处理
for(int i=2;i<N;++i) fb[i]=(fb[i-1]+fb[i-2])%P,inv_fb[i]=qpow(fb[i],P-2); //求逆元
for(int i=1;i<N;++i) h[i]=1;
for(int i=1;i<N;++i){
if(!mu[i])continue;
for(int j=1;j*i<N;++j)
h[j*i]=h[j*i]*(mu[i]==1?fb[j]:inv_fb[j])%P; //注意mu可能为负一
}
mul[0]=1;
for(int i=1;i<N;++i) mul[i]=(mul[i-1]*h[i])%P;
}
LL get_ans(int x,int y){
LL res=1,as;
int _t=min(x,y);
for(int l=1,r;l<=_t;l=r+1){
r=min(_t,min(g(x,l),g(y,l)));
as=mul[r]*qpow(mul[l-1],P-2)%P; //前缀积得底数
res=res*qpow(as,1ll*(x/l)*(y/l)%(P-1))%P;
}
return res;
}
int main(){
pred();
int T;
scanf("%d",&T);
int n,m;
while(T--){
scanf("%d%d",&n,&m);
printf("%lld\n",get_ans(n,m));
}
return 0;
}

补充:变换方法

下面记录一些常见的变换方法:

  • gcd(i,j)==1d|id|jμ(d)

  • 像例题四一样的完全展开预处理无关项

  • 像例题二一样 d(ij)=x|iy|j[gcd(x,y)==1]

  • 改变未知数使得两个 分离,预处理其中一个(也可以看作将两数积设出,继续化简)

    YY的GCD,莫反后得
    kprimed=1min(N,M)kμ(d)NkdMkd
    若直接计算,对于每个询问,最坏时间复杂度为 O(n) 会TLE,可以变化如下:

    (44)T=kd(45)则原式=kprimesd=1min(N,M)kμ(d)NTMT(46)=T=1min(N,M)k|Tkprimeμ(Tk)NTMT(47)=T=1min(N,M)NTMTk|Tkprimeμ(Tk)

    后面的 与询问无关,可以预处理

  • 注意卷积中的一些常用等式,如 Idμ=φ

    (48)Ans=i=1nj=1ngcd(i,j)(49)莫反得Ans=r=1nnrnrd|rdμ(rd)(50)=r=1nnrnrφ(r)