莫比乌斯反演学习笔记
莫比乌斯函数
定义
性质
莫比乌斯函数是积性函数。
其中 \(\varepsilon(n) = [n = 1], 1(n) = 1\)。\(*\) 表示狄利克雷卷积。
证明:
设 \(n = \sum\limits_{i=1}^k {p_i}^{e_i}, n' = \sum\limits_{i=1}^k p_i\)。
则有
补充结论:
显然。
线性求莫比乌斯函数
由于莫比乌斯函数是积性函数,故可以用线性筛求。
具体实现如下:
mu[1]=1;
for (int i=2;i<=n;i++) {
if (!f[i]) prime[++cnt]=i,mu[i]=-1;
for (int j=1;j<=cnt&&prime[j]*i<=n;j++) {
f[prime[j]*i]=1;
if (i%prime[j]==0) {
mu[prime[j]*i]=0;
break;
} else mu[prime[j]*i]=-mu[i];
}
}
莫比乌斯反演
设 \(f(n), g(n)\) 为两个数论函数。
形式一:
这种形式下, \(f(n)\) 被称为 \(g(n)\) 的莫比乌斯变换,\(g(n)\) 被称为 \(f(n)\) 的莫比乌斯反演。
容易发现当 \(f(n) = \varepsilon(n), g(n) = \mu(n)\) 时满足这个结论。
形式二:
例题
洛谷P2522 [HAOI2011]Problem b
每个区间左右端点都 \(\div k\),然后就变成了求 \(\gcd(x, y) = 1\)。
然后是经典 Trick 拆成四个类似的式子,然后推导以下:
然后数论分块优化一下即可。
// Think twice,code once.
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=50000;
int T,cnt,prime[50005],mu[50005];
bool f[50005];
int calc(int n,int m) {
int res=0;
for (int l=1,r;l<=min(n,m);l=r+1) {
r=min(n/(n/l),m/(m/l));
res+=(mu[r]-mu[l-1])*(n/l)*(m/l);
}
return res;
}
int main() {
mu[1]=1;
for (int i=2;i<=N;i++) {
if (!f[i]) prime[++cnt]=i,mu[i]=-1;
for (int j=1;j<=cnt&&prime[j]*i<=N;j++) {
f[prime[j]*i]=1;
mu[prime[j]*i]=-mu[i];
if (i%prime[j]==0) {mu[prime[j]*i]=0;break;}
}
}
for (int i=2;i<=N;i++) mu[i]+=mu[i-1];
scanf("%d",&T);
while (T--) {
int l1,r1,l2,r2,k;
scanf("%d%d%d%d%d",&l1,&r1,&l2,&r2,&k);
l1=(l1+k-1)/k;
r1=r1/k;
l2=(l2+k-1)/k;
r2=r2/k;
if (l1>r1||l2>r2) {puts("0");continue;}
printf("%d\n",calc(r1,r2)-calc(l1-1,r2)-calc(r1,l2-1)+calc(l1-1,l2-1));
}
return 0;
}
洛谷P2257 YY的GCD
与上一道题类似,可以简单地写出式子
然而这样做复杂度是 \(O(T \dfrac{n \sqrt{n}}{\ln n})\) 的,显然过不去。
考虑进一步优化:设 \(k = pd\),则:
然后后面这个式子可以预处理,于是复杂度就降到了 \(O(T \sqrt{n})\)。
// Think twice, code once.
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e7;
int T,n,m;
int cnt,prime[10000005],mu[10000005];
int f[10000005];
long long sum[10000005];
int main() {
mu[1]=1;
f[1]=1;
for (int i=2;i<=N;i++) {
if (!f[i]) prime[++cnt]=i,mu[i]=-1;
for (int j=1;j<=cnt&&(long long)prime[j]*i<=N;j++) {
f[prime[j]*i]=1;
if (i%prime[j]==0) {
mu[prime[j]*i]=0;
break;
} else mu[prime[j]*i]=-mu[i];
}
}
for (int i=1;i<=cnt;i++)
for (int j=prime[i];j<=N;j+=prime[i])
sum[j]+=mu[j/prime[i]];
for (int i=1;i<=N;i++) sum[i]+=sum[i-1];
scanf("%d",&T);
while (T--) {
scanf("%d%d",&n,&m);
long long ans=0;
for (int l=1,r;l<=min(n,m);l=r+1) {
r=min(n/(n/l),m/(m/l));
ans+=(sum[r]-sum[l-1])*(n/l)*(m/l);
}
printf("%lld\n",ans);
}
return 0;
}
洛谷P3327 [SDOI2015]约数个数和
反演来咯~
首先一个性质:
具体证明见 \(\color{black}{\text{S}}\color{red}{\text{iyuan}}\) 的博客。
于是:
到这里就可以用莫比乌斯函数的性质把 \([\gcd(x, y) = 1]\) 给拆掉了,但是这里介绍一种用反演的做法:
先把 \(x, y\) 替换成 \(i, j\),然后定义:
则:
设:
则:
易得答案为 \(f(1)\)。根据莫比乌斯反演形式二:
故:
发现当 \(d > \min(n, m)\) 时没有贡献,故:
求 \(s\) 和答案都可以使用数论分块,时间复杂度 \(O(T \sqrt{n})\)
// Think twice, code once.
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int T,n,m;
int cnt,prime[50005],mu[50005];
int f[50005];
long long sum[50005];
int main() {
mu[1]=1;
for (int i=2;i<=50000;i++) {
if (!f[i]) prime[++cnt]=i,mu[i]=-1;
for (int j=1;j<=cnt&&prime[j]*i<=50000;j++) {
f[prime[j]*i]=1;
if (i%prime[j]==0) {mu[prime[j]*i]=0;break;}
else mu[prime[j]*i]=-mu[i];
}
}
for (int i=2;i<=50000;i++) mu[i]+=mu[i-1];
for (int i=1;i<=50000;i++)
for (int l=1,r;l<=i;l=r+1) {
r=i/(i/l);
sum[i]+=(long long)(r-l+1)*(i/l);
}
scanf("%d",&T);
while (T--) {
scanf("%d%d",&n,&m);
if (n>m) swap(n,m);
long long ans=0;
for (int l=1,r;l<=n;l=r+1) {
r=min(n/(n/l),m/(m/l));
ans+=(mu[r]-mu[l-1])*sum[n/l]*sum[m/l];
}
printf("%lld\n",ans);
}
return 0;
}