莫比乌斯反演

莫比乌斯反演

整除分块(数论分块)

这是莫比乌斯反演的前置知识。

整除分块用于在 \(O(\sqrt n)\) 的时间复杂度内求解以下和式:

\[\sum_{i=1}^nf(i)\left\lfloor\frac ni\right\rfloor \]

其中 \(f(i)\) 是一个函数。如果暴力求解,复杂度为 \(O(n)\)。但是我们发现对于很多时候对于一个范围内的 \(i\)\(\lfloor n/i\rfloor\) 的值都是相同的,比如当 \(n=8\) 时,对于 \(i=5,6,7,8\)\(\lfloor n/i\rfloor\) 的值都是 \(1\)。如果我们能把类似这样的区间分成一个块统一计算,时间就快很多了。

现在我们需要证明两个问题:

  1. \(\lfloor n/i\rfloor\) 按照相同值分块少于 \(2\sqrt n\) 种,这决定了算法的复杂度。

    证明:

    • \(i\le\sqrt n\) 时,\(\lfloor n/i\rfloor\ge\sqrt n\),此时 \(\lfloor n/i\rfloor\)\(\sqrt n\) 种取值;
    • \(i>\sqrt n\) 时,\(\lfloor n/i\rfloor<\sqrt n\),此时 \(\lfloor n/i\rfloor\) 也有 \(\sqrt n\) 种取值。

    综上,总取值数不超过 \(2\sqrt n\) 种。证毕。

  2. 当我们知道每个块的第一个数 \(L\) 时,有 \(R=\left\lfloor\dfrac n{\lfloor n/i\rfloor}\right\rfloor\)。证明略。

于是就能在 \(O(\sqrt n)\) 的时间内解决这个问题了。

模板:UVA11526 H(n)

题意:求解 \(\sum_{i=1}^n\lfloor n/i\rfloor\)

#include<bits/stdc++.h>
using namespace std;

int main(){
	int T,n;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		long long ans=0;
		for(long long l=1,r;l<=n;l=r+1){
			r=n/(n/l);
			ans+=1ll*(r-l+1)*(n/l);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

应用:约数和问题

题意:求解 \(\sum_{i=L}^R\sigma(i)\),其中 \(\sigma(n)\)\(n\) 的约数和,即 \(\sum_{d\mid n}d\)

显然原式可以写为 \(\mathit{sum}(R)-\mathit{sum}(L-1)\),其中 \(\mathit{sum}(n)=\sum_{i=1}^n\sigma(i)\)。有一个重要结论:\(\mathit{sum}(n)=\sum_{i=1}^ni\times\lfloor n/i\rfloor\)。证明略。于是这个问题就能用整除分块解决了。

#include<bits/stdc++.h>
using namespace std;

long long sve(int n){
	long long ans=0;
	for(long long l=1,r;l<=n;l=r+1){
		r=n/(n/l);
		ans+=(l+r)*(r-l+1)*(n/l)>>1;
	}
	return ans;
}

int main(){
	int x,y;
	cin>>x>>y;
	cout<<sve(y)-sve(x-1)<<'\n';
	return 0;
}

前置芝士:线性筛

不仅要会线性筛,更要会用它处理一些基本函数。一般来说,线性筛能解决一些简单积性函数的预处理。

求解约数个数

这种问题需要我们在线性时间求出所有 \(d(i)\),其中 \(d(i)=\sum_{d\mid i}1\)

首先由算术基本定理,一个任意正整数 \(n\) 分解质因数可以得到

\[n=p_1^{a_1}p_2^{a_2}\dots p_m^{a_m} \]

枚举每个素因子的指数,容易得到 \(n\) 的约数个数为

\[d(n)=(1+a_1)(1+a_2)\cdots(1+a_m) \]

为了计算 \(d(i)\),我们需要在线性筛的时候记录 \(\mathit{num}(i)\),表示 \(i\) 的最小素因子的个数。然后分类讨论:

  • 当前数是素数,设置 \(d(i)=2\)\(\mathit{num}(i)=1\)
  • \(i\bmod j\ne0\),其中 \(j\) 是之前筛出来的一个素数。那么我们知道 \(i\times j\) 中一定会产生一个新的素因子 \(j\),所以有 \(d(i\times j)=d(i)\times d(j)=d(i)\times2\)\(\mathit{num}(i\times j)=1\)
  • \(i\bmod j=0\),此时 \(j\) 一定是 \(i\) 的最小素因子,且 \(i\times j\) 的最小素因子个数会 \(+1\),简单推导不难得到 \(d(i\times j)=d(i)/(\mathit{num}(i)+1)\times(\mathit{num}(i)+2)\)\(\mathit{num}(i\times j)=\mathit{num}(i)+1\)

问题得解。

求解约数和

这种问题需要我们在线性时间内求出所有 \(\sigma(i)\),其中 \(\sigma(i)=\sum_{d\mid i}d\)

首先需要得到关于 \(\sigma(n)\) 的式子为

\[\sigma(n)=(1+p_1+p_1^2+\cdots+p_1^{a_1})(1+p_2+p_2^2+\cdots p_2^{a_2})\cdots(1+p_m+p_m^2+\cdots p_m^{a_m}) \]

然后同理维护一个 \(\mathit{num}(i)\) 表示 \(i\) 的最小素因子 \(p_1\)\((1+p_1+p_1^2+\cdots+p_1^{a_1})\) 值。同样的推理不难得到:

  • 当前数是素数,设置 \(\sigma(i)=\mathit{num}(i)=i+1\)
  • \(i\bmod j\ne0\),则 \(\sigma(i\times j)=\sigma(i)\times\sigma(j)=\sigma(i)\times(j+1)\)\(\mathit{num}(i\times j)=j+1\)
  • \(i\bmod j=0\),则 \(\sigma(i\times j)=\sigma(i)/\mathit{num}(i)\times(\mathit{num}(i)\times j+1)\)\(\mathit{num}(i\times j)=\mathit{num}(i)\times j+1\)

问题得解。

莫比乌斯函数

莫比乌斯函数 \(\mu(n)\) 的定义为

\[\mu(n)=\begin{cases}1&n=1\\[1ex](-1)^m&n=p_1p_2\cdots p_m~,~\text{其中 }p_i\text{ 为不同的素数}\\[1ex]0&\text{其他} \end{cases} \]

性质

  • 性质一:莫比乌斯函数是积性函数,可以用线性筛求解:

    void prime(int n){
    	mu[1]=1;
    	for(int i=2;i<=n;i++){
    		if(!isp[i]) pri.emplace_back(i),mu[i]=-1;
    		for(auto j:pri){
    			if(i>n/j) break;
    			isp[i*j]=1;
                if(i%j==0) break;
                mu[i*j]=-mu[i];
    		}
    	}
    }
    

2025/6/30 upd. 其实还可以更简便地计算:

void init(int n){
	mu[1]=1;
	for(int i=1;i<=n;i++)
		for(int j=i<<1;j<=n;j+=i)
			mu[j]-=mu[i];
}
  • 性质二\(\forall n\in\mathbf N^*\) 满足

    \[\sum_{d\mid n}\mu(d)=[n=1] \]

    证明:

    显然当 \(n=1\) 时原式成立。

    \(n>1\) 时,记 \(F(n)=\sum_{d\mid n}\mu(d)\),由积性函数的性质有 \(F(n)=F(p_1^{a_1})F(p_2^{a_2})\cdots F(p_m^{a_m})\)。又因为有对于任意 \(F(p_i^{a_i})=\sum_{d\mid p^k}\mu(d)=\mu(1)+\mu(p_i)+\mu(p_i^2)+\cdots+\mu(p_i^{a_i})=1+(-1)+0+\cdots0=0\),故原命题得证。

  • 性质三\(\forall i,j\in\mathbf N^*\) 满足

    \[\sum_{d|\gcd(i,j)}\mu(d)=[\gcd(i,j)=1] \]

    其实就是性质二的一个推论。但它比较重要,许多式子都是由它推到莫比乌斯函数上。

莫比乌斯反演

\(f\) 是算术函数,\(F\)\(f\) 的和函数,即 \(F(n)=\sum_{d\mid n}f(d)\),则有

\[f(n)=\sum_{d\mid n}\mu(d)F\Big(\frac nd\Big) \]

其实在实际应用里面没有那么重要。

例题

P2257 YY的GCD

题意:求

\[\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)\in\mathbf P] \]

其中 \(\mathbf P\) 是所有素数构成的集合。

不妨设 \(n\le m\),化式子为:

\[\begin{aligned} \text{原式}&=\sum_{i=1}^n\sum_{j=1}^m\sum_{p\in\mathbf P}[\gcd(i,j)=p]\\ &=\sum_{i=1}^n\sum_{j=1}^m\sum_{p\in\mathbf P}\bigg[\text{gcd}\bigg(\frac ip,\frac jp\bigg)=1\bigg]\\ &=\sum_{p\in\mathbf P}\sum_{i=1}^{\lfloor n/p\rfloor}\sum_{j=1}^{\lfloor m/p\rfloor}[\gcd(i,j)=1]\\ &=\sum_{p\in\mathbf P}\sum_{i=1}^{\lfloor n/p\rfloor}\sum_{j=1}^{\lfloor m/p\rfloor}\sum_{d\mid\gcd(i,j)}\mu(d)\\ &=\sum_{p\in\mathbf P}\sum_{i=1}^{\lfloor n/p\rfloor}\sum_{j=1}^{\lfloor m/p\rfloor}\sum_{d=1}^{\lfloor n/p\rfloor}[d\mid i][d\mid j]\mu(d)\\ &=\sum_{p\in\mathbf P}\sum_{d=1}^{\lfloor n/p\rfloor}\mu(d)\sum_{i=1}^{\lfloor n/p\rfloor}[d\mid i]\sum_{j=1}^{\lfloor m/p\rfloor}[d\mid j]\\ &=\sum_{p\in\mathbf P}\sum_{d=1}^{\lfloor n/p\rfloor}\mu(d)\bigg\lfloor\frac n{pd}\bigg\rfloor\bigg\lfloor\frac m{pd}\bigg\rfloor \end{aligned} \]

发现分母有 \(p\)\(d\) 两个变量,难以直接用整除分块求解。为了能用整除分块求解,不妨设 \(T=pd\),然后枚举 \(T\),得到

\[\text{原式}=\sum_{T=1}^{\lfloor n/p\rfloor}\bigg\lfloor\frac nT\bigg\rfloor\bigg\lfloor\frac mT\bigg\rfloor\sum_{p\in\mathbf P\land p|T}\mu\bigg(\frac Tp\bigg) \]

前半部分可以整除分块,后半部分只要用线性筛预处理即可。

#include<bits/stdc++.h>
using namespace std;

constexpr int MAXN=1e7+5;
bool isp[MAXN];
int mu[MAXN],smu[MAXN];
vector<int>pri;

void prime(int n){
	mu[1]=1;
	for(int i=2;i<=n;i++){
		if(!isp[i]) pri.emplace_back(i),mu[i]=-1;
		for(auto j:pri){
			if(i>n/j) break;
			isp[i*j]=1;
			mu[i*j]=(i%j?-mu[i]:0);
			if(i%j==0) break;
		}
	}
	for(auto i:pri)
		for(int j=1;j<=n/i;j++)
			smu[i*j]+=mu[j];
	for(int i=1;i<=n;i++) smu[i]+=smu[i-1];
}

int main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	prime(1e7);
	int T,n,m;
	cin>>T;
	while(T--){
		cin>>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+=1ll*(smu[r]-smu[l-1])*(n/l)*(m/l);
		}
		cout<<ans<<'\n';
	}
	return 0;
}

P3327 [SDOI2015] 约数个数和

这题主要在于知道 \(d\) 函数的一个性质:

\[d(ij)=\sum_{x\mid i}\sum_{y\mid j}[\gcd(x,y)=1] \]

然后就能套路化式子了。

P3704 [SDOI2017] 数字表格

这题是莫比乌斯反演用在求积的形式。推式子:

\[\begin{aligned} \text{原式}&=\prod_{p=1}^nf(p)^{\sum\limits_{i=1}^n\sum\limits_{j=1}^m[\gcd(i,j)=p]}\\ &=\prod_{p=1}^nf(p)^{\sum\limits_{T=1}^n\lfloor\frac nT\rfloor\lfloor\frac mT\rfloor\sum\limits_{p|T}\mu(\frac Tp)} \end{aligned} \]

实际上它就是把整除分块的过程由加和改为乘积,预处理逆元加快速幂就可以处理。注意要用扩展欧拉定理处理负指数幂的情况。

#include<bits/stdc++.h>
using namespace std;

using ll=long long;
constexpr int MAXN=1e6+5;
constexpr ll MOD=1e9+7;
ll f[MAXN],smu[MAXN],fac[MAXN],inv[MAXN];
bool isp[MAXN];
int mu[MAXN];
vector<int>pri;
bool fl;

ll power(ll a,ll b){
	b=(b+MOD-1)%(MOD-1);
	ll res=1;
	for(;b;a=a*a%MOD,b>>=1)if(b&1)res=res*a%MOD;
	return res;
}
void init(int n){
	f[1]=mu[1]=1;
	for(int i=2;i<=n;i++){
		f[i]=(f[i-1]+f[i-2])%MOD;
		if(!isp[i]) pri.emplace_back(i),mu[i]=-1;
		for(auto j:pri){
			if(i>n/j) break;
			isp[i*j]=1;
			mu[i*j]=(i%j?-mu[i]:0);
			if(i%j==0) break;
		}
	}
	for(int i=0;i<=n;i++) smu[i]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n/i;j++)
			smu[i*j]=smu[i*j]*power(f[i],mu[j])%MOD;
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*smu[i]%MOD;
	inv[n]=power(fac[n],MOD-2);
	for(int i=n-1;~i;i--) inv[i]=inv[i+1]*smu[i+1]%MOD;
}

int main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	init(1e6);
	int T;
	ll n,m;
	cin>>T;
	fl=1;
	while(T--){
		cin>>n>>m;
		if(n>m) swap(n,m);
		ll ans=1;
		for(int l=1,r;l<=n;l=r+1){
			r=min(n/(n/l),m/(m/l));
			ans=ans*power(fac[r]*inv[l-1]%MOD,(n/l)*(m/l))%MOD;
		}
		cout<<ans<<'\n';
	}
	return 0;
}
posted @ 2025-03-15 22:31  Laoshan_PLUS  阅读(21)  评论(0)    收藏  举报