莫比乌斯反演并没有你看到的其他博客里写的那么复杂,那么多公式,很简单的
经典例题 :
可以看一下这题的其他做法
假设你已经知道了莫比乌斯函数
那么莫比乌斯反演也就是两个公式
设
为所求函数,
为构造出来求
的中间函数 , 有
为了方便, 这里我们称第一个公式为 约数反演, 因为f函数的自变量为所求n的因数. 同样的, 称第二个为 倍数反演
看不懂公式不要紧, 手模一遍即可 : 约数反演 : 构造出 有 倍数反演 : 构造出 有 |
看到这里, 你是不是觉得你已经可以用莫比乌斯反演解决上面的例题了呢?
所求g(n), 且n为两个数的gcd, 那么很明显我们要构造的f()的自变量应该是n的倍数, 所以采用倍数反演
令
那么此时的f(n)的含义不是显而易见的吗?
, 这些加起来不就相当于你从i选一个n的倍数,j选一个n的倍数吗?
那么就有 也就是说,
接下来是 的问题, 这样安排是为了让初学者快速的了解莫比乌斯反演, 而不是花时间卡在这种理论性的东西
莫比乌斯函数
μ(x)的值 | 情况 |
---|---|
0 | 存在一个q>1 |
1 | k为偶数(包括0) |
-1 | k为奇数 |
看得出来, 是一个和素数有关的积性函数, 所以是可以用素数筛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;
}
}
}
}
的性质 (拓展):
例题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);
}