莫比乌斯反演小结——套路的胜利

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hwzzyr/article/details/81010954

莫比乌斯反演套路

简介&&吐槽

  莫比乌斯反演,通常又可以称为“懵逼钨丝繁衍”

​ 这样一种听上去就很高端的知识是做什么的?

莫比乌斯反演是数论数学中很重要的内容,可以用于解决很多组合数学的问题。

  这和没说有什么区别……事实上,莫比乌斯反演充满了套路。
  我们开心的来看一下它的定义吧。
  不过在这之前,我们先补一些有趣的小知识。


1.数论函数的定义

在数论上,算术函数(或称数论函数)指定义域为正整数、陪域为复数的函数,每个算术函数都可视为复数的序列。

就我们oi选手而言,简单的理解为定义域为正整数的函数即可。

2.积性函数与完全积性函数

积性函数:对于所有 g c d ( a , b ) = 1 ,满足 f ( a b ) = f ( a ) f ( b )
完全记性函数:对于所有 a , b ,满足 f ( a b ) = f ( a ) f ( b )

3.常见的积性函数

3.1 欧拉函数 φ

φ ( n ) 表示 [ 1 , n ] 中与 n 互质的数的个数
φ ( n ) = n p P p 1 p ,其中, P n 的不同因子集合

3.2 莫比乌斯函数 μ

n 有平方因子,则 μ ( n ) = 0
否则, n k 个质因数的乘积,则 μ ( n ) = ( 1 ) k

3.3 幂函数 I d

I d k ( n ) 表示 n k
特别的,有 I d 0 ( n ) = 1 ( n ) = 1

I d 1 ( n ) = I d ( n ) = n

扫描二维码关注公众号,回复: 3296848 查看本文章
3.4 单位函数 ϵ

ϵ ( n ) = { 0 ( n > 1 ) 1 ( n = 1 )
这个函数如果用群论的思想来理解的话就相当于一个幺元。

4. D i r i c h l e t   C o n v o l u t i o n

——狄利克雷卷积

定义两数论函数 f , g D i r i c h l e t 卷积

( f g ) ( n ) = d | n f ( d ) g ( n d )

注意:卷积 ( f g ) ( n ) 中的 不是乘号,而是卷积的表示

4.1 D i r i c h l e t 卷积的性质

交换律: f g = g f
结合律: f ( g h ) = ( f g ) h
分配率: f ( g + h ) = f g + f h
单位元: f ϵ = f
f , g 均为积性函数,则 ( f g ) 仍为积性函数
具体的证明略去(太难写了)

4.2 常见的 D i r i c h l e t 卷积

我们在这里只列举常用的两个卷积式子
φ ( n ) = d | n μ ( d ) n d ,即 φ ( n ) = ( μ I d ) ( n )
ϵ ( n ) = d | n μ ( d ) ,即 ϵ ( n ) = ( μ 1 ) ( n )
我们可以证明一下第二个式子(这个式子在后面很常用)
  假设 n k 个不同的质因子,则有

d | n μ ( d ) = i = 0 k ( 1 ) i ( i k )

  我们来理解一下这个式子。
  首先,如果 n 的因数 d 中含有平方因子,则显然有 μ ( d ) = 0 ,可以从和式中略去
  那么对于一个数 d = p 1 p 2 p m (其中, p 1 , p 2 , p m d 的质因子),根据 μ 函数的定义,显然有 μ ( d ) = ( 1 ) m
  那么从 n k 个质因子中任意选出 m 个之后,这些质因子的乘积设为 x ,一定有 μ ( x ) 的值均为 ( 1 ) m ,并且这样的方案恰有 ( m k ) 种。
  它们的总和就得到了 i = 0 k ( 1 ) i ( i k )
  显然有 i = 0 k ( 1 ) i ( i k ) = i = 0 k ( 1 ) i ( 1 ) k i ( i k )
  根据二项式定理,我们最后得到
  
d | n μ ( d ) = i = 0 k ( 1 ) i ( i k ) = ( 1 1 ) k

  这个式子当且仅当 n = 1 时等于 1 ,原式得证。

5.莫比乌斯反演

5.1 莫比乌斯反演的形式

如果有两个函数 f , g ,满足

f = d | n g ( d )

则它们也满足
g = d | n g ( d ) μ ( n d )

反之亦然,即
f = g 1 g = μ f

5.2 莫比乌斯反演的证明

网上常见的证明方式都是用和式的变换来做的。我们今天就用 D i r i c h l e t 卷积来证明
  设

f = g 1

  我们将等式两侧同时卷上 μ ,得
μ f = g 1 μ

  
μ f = g ϵ = g

  (这一步我们刚才在常见的 D i r i c h l e t 卷积中证明过)
  整理得
g = f μ

  反过来同理
这样看来莫比乌斯反演的正确性就非常显然了,只要我们确信 μ 1 = ϵ 即可

5.3 莫比乌斯反演的应用方式

事实上,我们的莫比乌斯反演通常是用来改变枚举的顺序,从而达到简化计算的目的。
举一个抽象的例子,我们已知 f = ( g 1 ) ( n ) ,如果说 f 是一个易于求解的函数,那么我们就可以通过反演的形式 g = ( f μ ) ( n ) 来求得g的函数值。

6.莫比乌斯函数的常见套路

6.1改变和式的枚举顺序

已知 F ( n ) = i = 1 n d | i f ( d ) g ( i d )

  F ( n ) = d = 1 n i = 1 n d f ( d ) g ( i )   F ( n ) = d = 1 n f ( d ) i = 1 n d g ( i )

S ( n ) = i = 1 n g ( i ) ,则原式得

F ( n ) = d = 1 n f ( d ) S ( n d )

6.2 由 ϵ 到莫比乌斯反演

首先,我们常常会看到这样的一种表达 [ g c d ( i , j ) == 1 ]
事实上,这个记号 [ ] 可以等价于 ϵ ( )
例如:
已知 i = 1 n j = 1 m [ g c d ( i , j ) == 1 ]
由于 ϵ = μ 1
i = 1 n j = 1 m d | g c d ( i , j ) μ ( d )
此时,根据套路1,改变枚举顺序,首先枚举 d
d = 1 m i n ( n , m ) μ ( d ) i = 1 n d i = 1 m d 1   d = 1 m i n ( n , m ) μ ( d ) n d m d

这个式子就算是暴力枚举也可以在 O ( n ) 的时间内完成,如果了解数论分块的小伙伴还可以在 O ( n ) 的时间内求出答案。

6.3将复合的条件提出

接下来,我们的套路就会给出一些例题了

bzoj1101 [POI2007]Zap

Description

FGD正在破解一段密码,他需要回答很多类似的问题:对于给定的整数a,b和d,有多少正整数对x,y,满足x<=a,y<=b,并且gcd(x,y)=d。作为FGD的同学,FGD希望得到你的帮助。

Input

第一行包含一个正整数n,表示一共有n组询问。(1<=n<= 50000)接下来n行,每行表示一个询问,每行三个正整数,分别为a,b,d。(1<=d<=a,b<=50000)

Output

对于每组询问,输出到输出文件zap.out一个正整数,表示满足条件的整数对数。

Sample Input

2
4 5 2
6 4 3

Sample Output

3
2

Hint

对于第一组询问,满足条件的整数对有(2,2),(2,4),(4,2)。对于第二组询问,满足条件的整数对有(6,3),(3,3)。

简明题意:给定 n , m , d ,求 i = 1 n j = 1 m [ g c d ( i , j ) == d ]
我们发现这个东西和套路2特别像,只不过区别是 [ g c d ( i , j ) == d ] 还是 [ g c d ( i , j ) == 1 ]
由于我们的 ϵ 函数只能够表达 [ g c d ( i , j ) == 1 ] 的情况,所以我们这里就要把原式转换为这种形式。
首先,我们先给出一个简单的式子:
n = n d , m = m d ,那么

g c d ( n , m ) = d g c d ( n , m ) = 1

这个东西特别显然,如果不能够很快接受的话可以想一下辗转相除法。
那么我们就可以把这个问题转换成为套路2了
i = 1 n j = 1 m [ g c d ( i , j ) == d ]

i = 1 n d j = 1 m d [ g c d ( i , j ) == 1 ]

套用套路2
t = 1 m i n ( n d , m d ) μ ( t ) n d t m d t

所以我们就可以在 O ( n ) 内解决问题

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=50005;
inline int read() {
    char c; int rec=0;
    while((c=getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
    return rec;
}
int np[Maxn],prime[Maxn],miu[Maxn],sum[Maxn],psize;
void Getlist() {
    sum[1]=miu[1]=1;
    for(int i=2;i<=50000;++i) {
        if(np[i]==0) {
            prime[++psize]=i; miu[i]=-1;
        }
        for(int j=1;j<=psize&&i*prime[j]<=50000;++j) {
            np[i*prime[j]]=1;
            if(i%prime[j]==0){miu[i*prime[j]]=0; break;}
            miu[i*prime[j]]=-miu[i];
        }
        sum[i]=sum[i-1]+miu[i];
    } return ;
}
int main() {
    int ans;
    int n=read(); Getlist();
    for(int i=1;i<=n;++i) {
        int a=read(),b=read(),d=read(),last,j;
        a/=d; b/=d; if(a>b) swap(a,b); ans=0;
        for(j=1;j*j<=a;++j) ans+=(a/j)*(b/j)*miu[j];
        for(;j<=a;j=last+1) {
            last=min(a/(a/j),b/(b/j));
            ans+=(sum[last]-sum[j-1])*(a/j)*(b/j);
        }
        cout<<ans<<'\n';
    }
    return 0;
};
int main() {
    return 0;
}

例题时间到
【HAOI2011】Problem b
显然,差分一下就可以了
【BZOJ 2818】Gcd
我们直接枚举所有的质数,然后再用套路3,时间复杂度。。。。总之小于 O ( n n )

6.4 枚举两数之积

我们首先来看一个常见的经典题目

[bzoj2820] YY的GCD

Description

神犇YY虐完数论后给傻×kAc出了一题
给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对
kAc这种傻×必然不会了,于是向你来请教……
多组输入

Input

第一行一个整数T 表述数据组数
接下来T行,每行两个正整数,表示N, M

Output

T行,每行一个整数表示第i组数据的结果

Sample Input

2
10 10
100 100

Sample Output

30
2791

Hint

T = 10000
N, M <= 10000000

这个题和之前的套路3有点像,很容易想到直接枚举所有的质数然后套用套路2来解决问题

但是问题来了:多组询问……

我们能不能够做到更快的解决问题呢?

我们来大力推一下式子(套路)

i = 1 n j = 1 m [ g c d ( i , j ) == p r i m e ]

套路3,枚举一下质数p

p P r i m e i = 1 n p j = 1 m p [ g c d ( i , j ) == 1 ]

套路2,将最后的 ϵ 式子改成有关 μ 的卷积

p P r i m e d = 1 m i n ( n p , m p ) μ ( d ) S ( n d p ) S ( m d p )

这就是我们在做上一道题【BZOJ 2818】Gcd 最后化出来的式子

但是这样显然还不够优秀啊,一次询问就是。。。大概接近 O ( n ) 的时间复杂度, T 次询问之后就会超时然后gg

所以我们看到了 d p 这个神奇的变量(雾)

T = d p ,那么式子可以暂时先化一步

p P r i m e d = 1 m i n ( n p , m p ) μ ( d ) S ( n T ) S ( m T )

套路1,首先枚举T

T = 1 n S ( n T ) S ( m T ) k | T , k P r i m e μ ( T k )

23333,套路嘛,肯定是要一个套一个的。我们的套路4就很好的证明了这一点,套路123叠加就成为了套路4。

现在这个式子,我们只要能够预处理出 k | T , k P r i m e μ ( T k ) 这一段就能够在 O ( n ) 的时间内解决每一次询问了。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=10000005;
inline int read() {
    char c; int rec=0;
    while((c=getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
    return rec;
}
int T,n,m;
int np[Maxn],prime[Maxn],miu[Maxn],sum[Maxn],g[Maxn],psize;
void Getlist() {
    miu[1]=1;
    for(int i=2;i<=10000000;++i) {
        if(np[i]==0) {
            prime[++psize]=i; miu[i]=-1;
        }
        for(int j=1;j<=psize&&i*prime[j]<=10000000;++j) {
            np[i*prime[j]]=1;
            if(i%prime[j]==0){miu[i*prime[j]]=0; break;}
            miu[i*prime[j]]=-miu[i];
        }
    }
    for(int i=1;i<=psize;++i)
        for(int j=1;j*prime[i]<=10000000;++j)
            g[j*prime[i]]+=miu[j];
    for(int i=1;i<=10000000;++i) sum[i]=g[i]+sum[i-1];
    //预处理出后面那块式子的前缀和
    return ;
}
int main() {
    long long ans;
    T=read(); Getlist();
    while(T--) {
        n=read(); m=read();
        if(n>m) swap(n,m);
        int last,i; ans=0;
        for(i=1;i*i<=n;++i) ans+=(long long)(n/i)*(m/i)*g[i];
        for(;i<=n;i=last+1) {
            last=min(n/(n/i),m/(m/i));
            ans+=(long long)(sum[last]-sum[i-1])*(n/i)*(m/i);
        }
        cout<<ans<<'\n';
    }
    return 0;
}

那么怎样才能够掌握求出后面这一块式子的套路呢?

当然是吃瓜群众我们喜闻乐见的套路5啦。

6.5 暴力计算一类函数的前缀和

我们假设有这样一个函数 h ( n ) = ( f g ) ( n ) ,换句话说就是 h ( n ) = d | n f ( d ) g ( n d )

那么计算 T ( n ) = i = 1 n h ( i ) 的暴力方案来了:

f o r i = 1 t o n

f o r j = 1 t o n i

h ( i j ) = h ( i j ) + f ( i ) g ( j )

f o r i = 1 t o n

T ( i ) = T ( i 1 ) + h ( i )

这个暴力算法的时间复杂度是多少呢?

O ( i = 1 n n i ) = O ( n i = 1 n 1 i ) = O ( n   l n   n )

回到之前的题目,我们要处理的式子就是

T ( n ) = i = 1 n k | i , k P r i m e μ ( i k )

这个例子其实并不标准,因为只有质数我们才会加入处理。

所以我们的第一个循环并不需要枚举每一个可能的因数 i ,只用枚举 [ 1 , n ] 范围内的质数即可。

由于 [ 1 , n ] 中的质数有 n l n   n 个,最后这个式子可以在 O ( n l n   n l n   n ) = O ( n ) 的时间内预处理完毕。

6.6 套路小结

这里就先写这些套路。事实上还有其他的的一些套路(比如有关 φ 函数的套路, l c m 的套路),但是基本上都可以从这5个基本套路中提炼出来。

当然,除了套路,对题目本身的敏感和对数学原理的了解程度都能够助你完成题目。

就到这里了。

猜你喜欢

转载自blog.csdn.net/hwzzyr/article/details/81010954