莫比乌斯反演学习笔记

莫比乌斯函数

定义

μ(n)={1n=10n 含有平方因子(1)k其中 k 为 n 本质不同的质因子个数

性质

莫比乌斯函数是积性函数。

dnμ(d)=ε(n)μ1=ε

其中 ε(n)=[n=1],1(n)=1 表示狄利克雷卷积。

证明:

n=i=1kpiei,n=i=1kpi

则有

dnμ(d)=dnμ(d)=i=0k(ki)(1)i=i=0k(ki)(1)i1ki=(1+1)k=[n=1]

补充结论:

dgcd(i,j)μ(d)=[gcd(i,j)=1]

显然。

线性求莫比乌斯函数

由于莫比乌斯函数是积性函数,故可以用线性筛求。

具体实现如下:

mu[1]=1for (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)=dng(d)g(n)=dnμ(d)f(nd)

这种形式下, f(n) 被称为 g(n) 的莫比乌斯变换,g(n) 被称为 f(n) 的莫比乌斯反演。

容易发现当 f(n)=ε(n),g(n)=μ(n) 时满足这个结论。

形式二:

f(n)=ndg(d)g(n)=ndμ(dn)f(d)

例题

洛谷P2522 [HAOI2011]Problem b

Link

每个区间左右端点都 ÷k,然后就变成了求 gcd(x,y)=1

然后是经典 Trick 拆成四个类似的式子,然后推导以下:

x=1ny=1m[gcd(x,y)=1]=x=1ny=1mdgcd(x,y)μ(d)=d=1min(n,m)μ(d)x=1n[dx]y=1m[dy]=d=1min(n,m)μ(d)ndmd

然后数论分块优化一下即可。

// 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

Link

与上一道题类似,可以简单地写出式子

pPx=1ny=1m[gcd(x,y)=p]=pPd=1min(n,m)pμ(d)npdmpd

然而这样做复杂度是 O(Tnnlnn) 的,显然过不去。

考虑进一步优化:设 k=pd,则:

pPd=1min(n,m)pμ(d)npdmpd=pPd=1min(n,m)pμ(d)nkmk=k=1min(n,m)nkmkpk,pPμ(kp)

然后后面这个式子可以预处理,于是复杂度就降到了 O(Tn)

// 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]约数个数和

Link

反演来咯~

首先一个性质:

d(nm)=xnym[gcd(x,y)=1]

具体证明见 Siyuan博客

于是:

i=1nj=1md(ij)=i=1nj=1mxiyj[gcd(x,y)=1]

到这里就可以用莫比乌斯函数的性质把 [gcd(x,y)=1] 给拆掉了,但是这里介绍一种用反演的做法:

i=1nj=1mxiyj[gcd(x,y)=1]=x=1ny=1mnxmy[gcd(x,y)=1]

先把 x,y 替换成 i,j,然后定义:

f(x)=i=1nj=1mnimj[gcd(i,j)=x]g(x)=xdf(d)

则:

g(x)=i=1nj=1mnimj[xgcd(i,j)]=i=1nxj=1mxnximxj=i=1nxnxij=1mxmxj

设:

s(x)=i=1xxi

则:

g(x)=s(nx)s(mx)

易得答案为 f(1)。根据莫比乌斯反演形式二:

f(n)=ndμ(dn)g(d)

故:

f(1)=1dμ(d1)g(d)=d=1μ(d)g(d)

发现当 d>min(n,m) 时没有贡献,故:

f(1)=i=1min(n,m)μ(i)g(i)=i=1min(n,m)μ(i)s(ni)s(mi)

s 和答案都可以使用数论分块,时间复杂度 O(Tn)

// 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;
}
posted @   Mine_King  阅读(55)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示