一.莫比乌斯函数:
1.定义:
μ(d)=⎩⎪⎨⎪⎧1,(−1)k,0,d=1d=p1∗p2∗...∗pk,p1,p2,...均为互异质数其他情况
2.性质:
(1) 积性函数,即如果满足 a 和 b 互质,那么就会满足 f(a∗b)=f(a)∗f(b);
(2) 对任意的正整数 n ,当 n>1时,其所有因子的莫比乌斯函数值相加为0,
即:
d∣n∑μ(d)={1,0, n=1 n>1
证明:
当 n=1 时,显然成立;
当 n>1 时,
首先,有:
μ(d)=0⟺d=p1∗p2∗...∗pk
对于 μ(d)=0 的因子,我们可以不用考虑,下面仅对 μ(d)=0 的因子讨论。
那么对于一个含有 r 的质因子的因子 d, μ(d)=Ckr∗(−1)r。
所以
d∣n∑μ(d)=i=0∑kCki∗(−1)i
又根据二项式定理:
(x+y)n=i=0∑nCni∗xi∗yn−i
令 x=−1,y=1,有
i=0∑kCki∗(−1)i=i=0∑kCki∗xi∗yk−i=0
所以,
d∣n∑μ(d)=0
即证。
(3) 对任意正整数 n ,有:
d∣n∑dμ(d)=nφ(n)
证明:
先证:
n=d∣n∑φ(d)
令:
f(n)=d∣n∑φ(d)
有:
f(n)∗f(m)=i∣n∑φ(i)∗j∣m∑φ(j)
=i∣n∑j∣m∑φ(i)∗φ(j)
因为欧拉函数为积性函数,所以:
=i∣n∑j∣m∑φ(i∗j)
=d∣nm∑φ(d)
=f(n∗m)
所以 f(n) 是积性函数。
根据唯一分解定理:
n=p1a1∗p2a2∗...∗pkak
所以:
f(n)=f(p1a1∗p2a2∗...∗pkak)=f(p1a1)∗f(p2a2)∗...∗f(pkak)
根据定义,有: f(pa)=φ(1)+φ(p)+φ(p2)+..+φ(pa)
由欧拉函数性质,当 n=pk ( p 为素数), φ(n)=pk−pk−1,有:
f(pa)=1+(p−1)+(p2−p)+...+(pa−pa−1)=pa
所以:
f(n)=p1a1∗p2a2∗...∗pkak=n
所以 n=d∣n∑φ(d)
知道这个后,就可以证明性质 (3) 了。
令 F(n)=n,f(n)=φ(n),
所以, F(n)=d∣n∑f(d)
很明显符合莫比乌斯反演的约数形式,
反演得: f(n)=d∣n∑μ(d)F(dn)=d∣n∑μ(d)∗dn
所以: f(n)=n∗d∣n∑dμ(d)
即: d∣n∑dμ(d)=nφ(n)
证毕。
3.线性筛法(基于欧拉筛)
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
vector<int>prime;
int mob[N];
bool vis[N];
void mobius()
{
prime.clear();
memset(vis,0,sizeof(vis));
mob[1]=1;//莫比乌斯函数第一类情况
for(int i=2;i<N;i++)
{
if(!vis[i])
{
prime.push_back(i);
mob[i]=-1;//素数
}
for(int j=0;j<prime.size()&&prime[j]*i<N;j++)
{
int tmp=i*prime[j];
vis[tmp]=1;
if(i%prime[j]==0)
{
mob[tmp]=0;//其他情况,因为prime[j]是i的因子,所以k的因子中含有的prime[j]的个数不是1
break;
}
else
mob[tmp]=-mob[i];//积性函数的性质
}
}
}
int main()
{
mobius();
for(int i=1;i<=10;i++)
cout<<mob[i]<<endl;
return 0;
}
二.莫比乌斯反演:
当一个函数 F(n) 很好求时,而其可以写成其 约数 或 倍数 d 的函数 f(d), f(d) 很不好求时,我们可以通过莫比乌斯反演利用 F(n) 反推 f(d),从而快速求解 f(d)。
F(n) 和 f(n) 是定义在非负整数集合上的两个函数。注意 n 和 d 不要弄混。
1.约数形式:
F(n)=d∣n∑f(d)⟹f(n)=d∣n∑μ(d)∗F(dn)
证明:
∑d∣nμ(d)∗F(dn)=∑d∣nμ(d)∗∑k∣dnf(k)
=∑k∣nf(k)∑d∣knμ(d) =f(n)
(k和d是等价的,原来是一个μ(d)对应多个f(k),现在是一个f(k)对应多个μ(d))
对于 ∑d∣knμ(d) ,由莫比乌斯函数的性质,当且仅 kn=1 时,其值等于1,否则等于0。
2.倍数形式:
F(n)=n∣d∑f(d)⟹f(n)=n∣d∑μ(nd)F(d)
证明(同理):
令 k=nd,
∑n∣dμ(nd)F(d)
=∑k=1+∞μ(k)F(n∗k)
=∑k=1+∞μ(k)∑nk∣tf(t)
=∑n∣tf(t)∑k∣ntμ(k)=f(n)
三.常见应用:
1. gcd(i,j) 相关问题:
一般先将其转化为求 ∑i=1n∑j=1m[gcd(i,j)==1] ,然后利用数论分块解决。
求解 ∑i=1n∑j=1m[gcd(i,j)==1] 的过程:
令 f(d)=∑i=1n∑j=1m[gcd(i,j)==d],num=min(n,m)
令 F(d)=f(d)+f(2d)+f(3d)+...+f(kd)=∑d∣nnumf(n)
即 F(d) 表示 gcd(i,j) 为 d 的倍数的二元组的个数,显然 F(d)=⌊dn⌋∗⌊dm⌋,容易求得。这样就可以通过 F(d) 来求 f(d)了。
由反演公式,得:
f(d)=∑d∣nnumμ(dn)F(n)=∑d∣nnumμ(dn)∗⌊nn⌋∗⌊nm⌋
(向下取整括号中,上面的 n,m 是范围,下面的 n 是变量,注意区分)
当 d=1时,有:(记住)
f(1)=∑i=1n∑j=1m[gcd(i,j)==1]=∑i=1min(n,m)μ(i)∗⌊in⌋∗⌊im⌋
先预处理出莫比乌斯函数的前缀和,然后用数论分块处理。
<mark>重点【反演解题套路】</mark>:
[gcd(i,j)=1]=d∣gcd(i,j)∑μ(d)=d∣i,d∣j∑μ(d)
常见应用过程:
∑i=1n∑j=1m[gcd(i,j)=1]
=∑i=1n∑j=1m∑d∣i,d∣jμ(d)
=∑d=1min(n,m)μ(d)∗⌊dn⌋∗⌊dm⌋(改变枚举顺序)
证明:
因为莫比乌斯函数性质 (2): [n=1]=d∣n∑μ(d)
问题:
1.求 ∑i=1n∑j=1m[gcd(i,j)=d](n≤m)
ANS=∑i=1n∑j=1m∑d∣gcd(i,j)μ(d)=∑i=1n∑j=1m∑d∣i,d∣jμ(d)
=∑d=1n(μ(d)∗⌊dn⌋∗⌊dm⌋)(改变枚举顺序,常用的技巧)
四.例题:
1.hdu1695:
先附上一篇详解和思路
主要是利用莫比乌斯反演的性质来构造函数,同时利用了莫比乌斯反演的第二个函数关系。还有思维的转化。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int mu[N];
bool vis[N];
vector<int>prime;
void mobius()
{
memset(mu,0,sizeof(mu));
memset(vis,0,sizeof(vis));
prime.clear();
mu[1]=1;
for(int i=2;i<N;i++)
{
if(!vis[i])
{
prime.push_back(i);
mu[i]=-1;
}
for(int j=0;j<prime.size()&&i*prime[j]<N;j++)
{
int tmp=i*prime[j];
vis[tmp]=1;
if(i%prime[j]==0)
{
mu[tmp]=0;
break;
}
else
mu[tmp]=-mu[i];
}
}
}
int main()
{
int T,cas=0;
mobius();
scanf("%d",&T);
while(T--)
{
int a,b,c,d,k;
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
printf("Case %d: ",++cas);
if(k==0)
{
puts("0");
continue;
}
b/=k;
d/=k;
long long ans1=0,ans2=0;
for(int i=1;i<=min(b,d);i++)
ans1+=(long long)mu[i]*(b/i)*(d/i);
for(int i=1;i<=min(b,d);i++)
ans2+=(long long)mu[i]*(min(b,d)/i)*(min(b,d)/i);
printf("%lld\n",ans1-ans2/2);
}
return 0;
}
2.hdu6390:
欧拉函数的性质+莫比乌斯函数性质的运用
已知:
求:
推导:
一直tle,把数据类型改了一下,竟然就过了,玄学。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
int n,m;
ll p,ans;
ll c[N],inv[N];
int prime[N];
bool vis[N];
int mu[N],phi[N];
void mobius_euler()
{
memset(vis,0,sizeof(vis));
mu[1]=1;
phi[1]=1;
int cnt=0;
for(int i=2;i<N;i++)
{
if(!vis[i])
{
prime[++cnt]=i;
mu[i]=-1;
phi[i]=i-1;
}
for(int j=1;j<=cnt&&i*prime[j]<N;j++)
{
int tmp=i*prime[j];
vis[tmp]=1;
if(i%prime[j]==0)
{
mu[tmp]=0;
phi[tmp]=phi[i]*prime[j];
break;
}
else
{
mu[tmp]=-mu[i];
phi[tmp]=phi[i]*phi[prime[j]];
}
}
}
}
void init()
{
inv[1]=1;
for(int i=2;i<=min(n,m);i++)//递推求逆元
inv[i]=inv[p%i]*(ll)(p-p/i)%p;
for(int i=1;i<=min(n,m);i++)
c[i]=(ll)i*inv[phi[i]]%p;
}
ll g(int x,int y)
{
ll res=0;
for(int i=1;i<=min(x,y);i++)
res=(res+(ll)mu[i]*(x/i)*(y/i))%p;
return res;
}
int main()
{
int t;
scanf("%d",&t);
mobius_euler();
while(t--)
{
scanf("%d%d%lld",&m,&n,&p);
ans=0;
init();
for(int i=1;i<=min(n,m);i++)
ans=(ans+c[i]*g(m/i,n/i))%p;
printf("%lld\n",ans);
}
return 0;
}
3.bzoj 2301:【莫比乌斯反演+容斥】
#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
vector<int>prime;
bool vis[N];
int mu[N],sum[N];
void mobius()
{
memset(vis,0,sizeof(vis));
memset(mu,0,sizeof(mu));
memset(sum,0,sizeof(sum));
prime.clear();
mu[1]=1;
for(int i=2;i<N;i++)
{
if(!vis[i])
{
prime.push_back(i);
mu[i]=-1;
}
for(int j=0;j<prime.size()&&i*prime[j]<N;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<N;i++)//莫比乌斯函数前缀和
sum[i]=sum[i-1]+mu[i];
}
int solve(int n,int m)
{
int k=min(n,m),res=0;
for(int l=1,r=1;l<=k;l=r+1)
{
r=min(k,min(n/(n/l),m/(m/l)));
res+=(sum[r]-sum[l-1])*(n/l)*(m/l);
}
return res;
}
int main()
{
int a,b,c,d,k,n;
mobius();
scanf("%d",&n);
while(n--)
{
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
int ans=solve(b/k,d/k)+solve((a-1)/k,(c-1)/k)-solve(b/k,(c-1)/k)-solve((a-1)/k,d/k);//容斥
printf("%d\n",ans);
}
return 0;
}
/* 2 2 5 1 5 1 1 5 1 5 2 */
4.bzoj2005-能量采集:
枚举d.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
bool vis[N];
int mu[N],sum[N];
vector<int>prime;
void mobius()
{
memset(vis,0,sizeof(vis));
memset(mu,0,sizeof(mu));
memset(sum,0,sizeof(sum));
prime.clear();
mu[1]=1;
for(int i=2;i<N;i++)
{
if(!vis[i])
{
prime.push_back(i);
mu[i]=-1;
}
for(int j=0;j<prime.size()&&i*prime[j]<N;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<N;i++)
sum[i]=sum[i-1]+mu[i];
}
ll solve(int n,int m)
{
int k=min(n,m);
int tn=n,tm=m,tk=k;
ll res=0;
for(int i=1;i<=k;i++)//gcd==i
{
tn=n/i;
tm=m/i;
tk=min(tn,tm);
for(int l=1,r=1;l<=tk;l=r+1)
{
r=min(min(tn/(tn/l),tm/(tm/l)),tk);
res+=((1LL)*(sum[r]-sum[l-1])*(tm/l)*(tn/l))*(1LL)*i;//注意*i,否则,只算了个数。
}
}
return res;
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
mobius();
printf("%lld\n",2*solve(n,m)-(1LL)*n*m);//防爆
}
return 0;
}
5.P2257 YY的GCD:
一开始的做法是直接暴力,先筛出 [1,1e7] 内的所有素数及莫比乌斯函数函数值,然后暴力枚举 min(n,m) 内的素数,进行数论分块。只过了一半的测试点就 t 了。
看了题解后发现还可以继续化简:
ANS=∑p=1min(n,m)isprime(p)∗∑i=1n∑j=1m[gcd(i,j)==p]
=∑p=1min(n,m)isprime(p)∗∑i=1⌊pn⌋∑j=1⌊pm⌋[gcd(i,j)==1](我到这步就直接算了)
=∑p=1min(n,m)isprime(p)∗∑d=1min(⌊pn⌋,⌊pm⌋)(μ(d)∗⌊d∗pn⌋∗⌊d∗pm⌋)
令 T=d∗p,t=p
=∑t=1min(n,m)isprime(t)∗∑t∣Tmin(n,m)(μ(t)∗⌊Tn⌋∗⌊Tm⌋)
=∑T=1min(n,m)⌊Tn⌋∗⌊Tm⌋∗∑t∣T,t∈primeμ(tT)
到此化简结束。
前半部分可以通过数论分块处理,后面可以在求莫比乌斯反演的时候预处理。
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e7+5;
vector<int> prime;
bool vis[N];
int mu[N];
ll sum[N],T[N];
void euler()
{
mu[1]=1;
for(int i=2;i<=N-5;i++)
{
if(!vis[i])
{
prime.push_back(i);
mu[i]=-1;
}
for(int j=0;j<prime.size()&&i*prime[j]<=N-5;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else
mu[i*prime[j]]=-mu[i];
}
}
for(int i=0;i<prime.size();i++)
{
for(int j=1;j*prime[i]<=N-5;j++)
T[j*prime[i]]+=mu[j];
}
for(int i=1;i<=N-5;i++)
sum[i]=sum[i-1]+T[i];
}
ll solve(int n,int m)
{
ll res=0;
int minn=min(n,m);
for(int l=1,r=1;l<=minn;l=r+1)
{
r=min(min(m/(m/l),n/(n/l)),minn);//cout<<"r= "<<r<<endl;
res+=1LL*(sum[r]-sum[l-1])*(n/l)*(m/l);
}
return res;
}
int main()
{
int t,n,m;
euler();//cout<<prime.size()<<endl;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
ll ans=solve(n,m);
printf("%lld\n",ans);
}
return 0;
}