莫比乌斯反演快速入门

莫比乌斯反演并没有你看到的其他博客里写的那么复杂,那么多公式,很简单的

经典例题 :

i = 1 N j = 1 M g c d ( i , j ) = n , g c d = n

可以看一下这题的其他做法

假设你已经知道了莫比乌斯函数 μ ( i )

那么莫比乌斯反演也就是两个公式
g ( n ) 为所求函数, f ( n ) 为构造出来求 g ( n ) 的中间函数 , 有

i f : f ( n ) = x | n g ( x ) , t h a t g ( n ) = x | n μ ( x ) f ( n x ) i f : f ( n ) = n | x g ( x ) , t h a t g ( n ) = n | x μ ( x n ) f ( x )

为了方便, 这里我们称第一个公式为 约数反演, 因为f函数的自变量为所求n的因数. 同样的, 称第二个为 倍数反演

看不懂公式不要紧, 手模一遍即可 :

约数反演 :
构造出 f ( 12 ) = g ( 1 ) + g ( 2 ) + g ( 3 ) + g ( 4 ) + g ( 6 ) + g ( 12 )
g ( 12 ) = μ ( 1 ) f ( 12 ) + μ ( 2 ) f ( 6 ) + μ ( 3 ) f ( 4 ) + μ ( 4 ) f ( 3 ) + μ ( 6 ) f ( 2 ) + μ ( 12 ) f ( 1 )

倍数反演 :
构造出 f ( 2 ) = g ( 2 ) + g ( 4 ) + g ( 6 ) + g ( 8 ) + g ( 10 ) + g ( 12 )
g ( 2 ) = μ ( 1 ) f ( 2 ) + μ ( 2 ) f ( 4 ) + μ ( 3 ) f ( 6 ) + μ ( 4 ) f ( 8 ) + μ ( 5 ) f ( 10 ) + μ ( 6 ) f ( 12 )


看到这里, 你是不是觉得你已经可以用莫比乌斯反演解决上面的例题了呢?

所求g(n), 且n为两个数的gcd, 那么很明显我们要构造的f()的自变量应该是n的倍数, 所以采用倍数反演

扫描二维码关注公众号,回复: 2965121 查看本文章

l i m i t , , l i m i t m i n ( N , M )

f ( n ) = g ( n ) + g ( 2 n ) + . . . g ( k n ) , k n <= l i m i t

那么此时的f(n)的含义不是显而易见的吗?

g ( n ) g c d n i j , g ( 2 n ) g c d 2 n i j , 这些加起来不就相当于你从i选一个n的倍数,j选一个n的倍数吗?

那么就有 f ( n ) = [ N n ] [ M n ] , 也就是说, O ( 1 ) f ( n ) , O ( l o g ) g ( n )


接下来是 μ ( i ) 的问题, 这样安排是为了让初学者快速的了解莫比乌斯反演, 而不是花时间卡在这种理论性的东西

莫比乌斯函数

: x = p 1 q 1 p 2 q 2 . . . p k q k

μ(x)的值 情况
0 存在一个q>1
1 k为偶数(包括0)
-1 k为奇数

看得出来, μ ( x ) 是一个和素数有关的积性函数, 所以是可以用素数筛O(n)预处理

int mu[N+9];
int pri[N+9>>1];int now;
bool vis[N+9];
void init(){
    memset(vis,false,sizeof(vis));
    now=0;
    mu[1]=1;
    for(int i=2;i<=N;i++){
        if(!vis[i]){
            pri[++now]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=now&&i*pri[j]<=N;j++){
            vis[i*pri[j]]=true;
            if(i%pri[j])mu[i*pri[j]]=-mu[i];//出现新的素数 mu=-mu
            else{
                mu[i*pri[j]]=0;             //出现相同素数
                break;
            }
        }
    }
}

μ ( x ) 的性质 (拓展):

d | n μ ( d ) = 0 ( n > 1 )
d | n μ ( d ) d = ϕ ( n ) n

例题1

给定整数N,求1<=x,y<=N且Gcd(x,y)为素数的数对(x,y)有多少对.

倍数公式了解一下

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e7;

int mu[N+9];
int pri[N+9>>1];int now;
bool vis[N+9];
void init(){
    memset(vis,false,sizeof(vis));
    now=0;
    mu[1]=1;
    for(int i=2;i<=N;i++){
        if(!vis[i]){
            pri[++now]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=now&&i*pri[j]<=N;j++){
            vis[i*pri[j]]=true;
            if(i%pri[j])mu[i*pri[j]]=-mu[i];//出现新的素数 mu=-mu
            else{
                mu[i*pri[j]]=0;             //出现相同素数
                break;
            }
        }
    }
}

int main(){
    init();
    int n;scanf("%d",&n);
    LL ans=0;
    for(int i=1;i<=now&&pri[i]<=n;i++){
        int p=pri[i];
        for(int j=p;j<=n;j+=p){
            LL f=(LL)((double)n/j)*(LL)((double)n/j);
            ans=ans+mu[j/p]*f;
        }
    }
    printf("%lld\n",ans);
}

例题2

GuGu函数 , 上面有大致题解只是求gcd=i的对数的方法变成了反演, 也是倍数反演

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e6;
LL mod;

int mu[N+9];
LL pri[N+9>>1];int now;
bool vis[N+9];
LL inv[N+9];
LL v[N+9];


void init(int n,int m){
    int lim=min(n,m);
    memset(vis,false,sizeof(bool)*(lim+3));
    now=0;mu[1]=1;inv[0]=inv[1]=1;v[1]=1;

    for(LL i=2;i<=lim;i++){
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        if(!vis[i]){
            pri[++now]=i;
            mu[i]=-1;
            v[i]=i*inv[i-1]%mod;
        }
        for(LL j=1;j<=now&&i*pri[j]<=lim;j++){
            vis[i*pri[j]]=true;
            if(i%pri[j])mu[i*pri[j]]=-mu[i],v[i*pri[j]]=v[i]*v[pri[j]]%mod;//出现新的素数 mu=-mu
            else{
                mu[i*pri[j]]=0;             //出现相同素数
                v[i*pri[j]]=v[i];
                break;
            }
        }
    }
}



int main(){
    //freopen("in.in","r",stdin);
    //freopen("out.out","w",stdout);
    int t;scanf("%d",&t);
    while(t--){

        LL n,m;scanf("%lld%lld%lld",&n,&m,&mod);
        init(n,m);
        LL ans=0;

        LL lim=min(n,m);
        for(LL i=1;i<=lim;i++){
            LL g=0;
            for(LL j=i;j<=lim;j+=i){
                LL f=(n/j)*(m/j);
                g=(g+mu[j/i]*f);
            }
            ans=(ans+g%mod*v[i]%mod)%mod;
        }

        printf("%lld\n",ans);
    }
    //printf("%.3fms\n",(double)clock()/CLOCKS_PER_SEC);


}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/82016719
今日推荐