莫比乌斯反演

有错请纠正,谢谢。

莫比乌斯函数

这个比较简单,前置知识 积性函数 积性函数 积性函数

积型函数是满足 ∀ a , b \forall a,b a,b,如果有 ( a , b ) = 1 (a,b)=1 (a,b)=1,都有 f ( a b ) = f ( a ) × f ( b ) f(ab)=f(a)\times f(b) f(ab)=f(a)×f(b)

完全积型函数是 ∀ a , b \forall a,b a,b 都有 f ( a b ) = f ( a ) × f ( b ) f(ab)=f(a)\times f(b) f(ab)=f(a)×f(b)

这里,我们首先对一个数 x x x 做他的标准质因数分解,记作: x = ∏ i = 1 k a i x=\displaystyle \prod_{i=1}^{k} a_i x=i=1kai

莫比乌斯函数为:

μ ( x ) = { 1 x = 1 ( − 1 ) k m a x ( a i ) = 1 0 o t h e r w i d e \mu(x)=\begin{array}{l} \left\{\begin{matrix} 1 \hspace{5em} x=1\\ (-1)^k \hspace{2em} max(a_i)=1\\ 0 \hspace{3em} otherwide\\ \end{matrix}\right. \end{array} μ(x)= 1x=1(1)kmax(ai)=10otherwide

莫比乌斯函数的一些性质:

①对于 ∀ n ∈ N + \forall n\in N^+ nN+,都有 ∑ d ∣ n μ ( d ) = [ n = 1 ] \displaystyle\sum_{d|n} \mu(d)=[n=1] dnμ(d)=[n=1]

②对于 ∀ n ∈ N + \forall n\in N^+ nN+,都有 ∑ d ∣ n μ ( d ) d = φ ( n ) n \displaystyle \sum_{d|n} \displaystyle \frac {\mu(d)} d=\displaystyle \frac {\varphi(n)} n dndμ(d)=nφ(n)

③对于 ∀ n ∈ P \forall n\in P nP,都有 μ ( n ) = − 1 \mu(n)=-1 μ(n)=1

#include<bits/stdc++.h>
using namespace std;
const int N=1e7+1000;
int mu[N],pri[N],tot;
bool npr[N];
void getmu(int n){
    memset(npr,0,sizeof npr);
    tot=0,mu[1]=1;
    for(int i=2;i<=n;++i){
        if(!npr[i])pri[++tot]=i,mu[i]=-1;
        for(int j=1;j<=tot&&i*pri[j]<=n;++j){
            npr[pri[j]*i]=1;
            if(i%pri[j])mu[i*pri[j]]=-mu[i];
            else break;
        }
    }
}
int m;
int main(){
	getmu(N); 
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		printf("%d\n",mu[i]);
	}
	return 0;
}

简短的线性筛筛莫比乌斯函数。

莫比乌斯反演

反演的作用是找出一个容易求解的函数 F ( x ) F(x) F(x),通过这个函数来求解其约数或倍数 d d d 的函数 f ( d ) f(d) f(d)

反演两种形式:

F ( n ) = ∑ d ∣ n f ( d ) → f ( n ) = ∑ d ∣ n μ ( n d ) F ( d ) F(n)=\displaystyle \sum_{d|n} f(d) \rightarrow f(n)= \displaystyle \sum_{d|n} \mu(\displaystyle \frac n d)F(d) F(n)=dnf(d)f(n)=dnμ(dn)F(d)

F ( n ) = ∑ n ∣ d f ( d ) → f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) F(n)=\displaystyle \sum_{n|d} f(d) \rightarrow \displaystyle f(n)=\sum_{n|d} \mu(\displaystyle \frac d n)F(d) F(n)=ndf(d)f(n)=ndμ(nd)F(d)

例题 1 1 1

ZAP-Queries

我们在说这道题目之前需要补充一个非常有名的恒等式。

[ gcd ⁡ ( i , j ) = 1 ] = ∑ d ∣ gcd ⁡ ( i , j ) μ ( d ) [\gcd(i,j)=1]=\displaystyle \sum_{d|\gcd(i,j)}\mu(d) [gcd(i,j)=1]=dgcd(i,j)μ(d)

证明略。

我们来看回这道题目:

其实就是求出 ∑ i = 1 a ∑ j = 1 b [ gcd ⁡ ( i , j ) = d ] \displaystyle \sum_{i=1}^a \displaystyle \sum_{j=1}^b [\gcd(i,j)=d] i=1aj=1b[gcd(i,j)=d]

我们可以先操作一步,将 gcd ⁡ \gcd gcd 中的 d d d 提取出来,然后就可以化成这样一个式子:(不妨设 a ≤ b a\le b ab

∑ i = 1 ⌊ a d ⌋ ∑ j = 1 ⌊ b d ⌋ [ gcd ⁡ ( i , j ) = 1 ] \displaystyle \sum_{i=1}^{\lfloor \frac {a} {d}\rfloor}\sum_{j=1}^{\lfloor \frac {b} {d}\rfloor} [\gcd(i,j)=1] i=1daj=1db[gcd(i,j)=1]

然后,使用我们刚刚的式子,就可以得到:

∑ i = 1 ⌊ a d ⌋ ∑ j = 1 ⌊ b d ⌋ ∑ k ∣ gcd ⁡ ( i , j ) μ ( k ) \displaystyle \sum_{i=1}^{\lfloor \frac {a} {d}\rfloor}\sum_{j=1}^{\lfloor \frac {b} {d}\rfloor} \sum_{k|\gcd(i,j)}\mu(k) i=1daj=1dbkgcd(i,j)μ(k)

我们可以将 i → ⌊ i k ⌋ i \rightarrow \lfloor \displaystyle \frac {i}{k}\rfloor iki j → ⌊ j k ⌋ j \rightarrow \lfloor \displaystyle \frac {j}{k}\rfloor jkj,这样就可以避免去枚举 k ∣ gcd ⁡ ( i , j ) k|\gcd(i,j) kgcd(i,j) 导致不必要的时间复杂度。

∑ k = 1 a ∑ i = 1 ⌊ a k d ⌋ ∑ j = 1 ⌊ b k d ⌋ μ ( k ) \displaystyle \sum_{k=1}^a\sum_{i=1}^{\lfloor \frac {a} {kd}\rfloor}\sum_{j=1}^{\lfloor \frac {b} {kd}\rfloor} \mu(k) k=1ai=1kdaj=1kdbμ(k)

我们发现 此时 μ ( k ) \mu(k) μ(k) 已经不受 i , j i,j i,j 的限制了。所以,我们可以直接将这个玩意儿提到 ∑ i = 1 ⌊ a k d ⌋ ∑ j = 1 ⌊ b k d ⌋ \sum_{i=1}^{\lfloor \frac {a} {kd}\rfloor}\sum_{j=1}^{\lfloor \frac {b} {kd}\rfloor} i=1kdaj=1kdb 之外,就可以得到:

a n s = ∑ k a μ ( k ) ⌊ a k d ⌋ ⌊ b k d ⌋ ans=\displaystyle \sum_{k}^a \mu(k)\lfloor \frac {a} {kd}\rfloor\lfloor \frac {b} {kd}\rfloor ans=kaμ(k)kdakdb

我们发现 ∑ k a μ ( k ) \displaystyle \sum_{k}^a \mu(k) kaμ(k) 这个玩意儿,是可以提前 O ( n ) O(n) O(n) 使用前缀和预处理的。

于是,代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e7+1000,M=1e6;
int prime[N],mu[N],cnt;
bool flag[N];
#define ll long long
ll tot[N],a,b,d;
void get_mu(){
	mu[1]=1; 
	for(int i=2;i<=M;i++){
		if(!flag[i])prime[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt&&prime[j]*i<=M;j++){
			flag[i*prime[j]]=true;
			if(i%prime[j]==0)break;
			mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<=M;i++){
		tot[i]=tot[i-1]+mu[i];
	}
}
ll solve(ll a,ll b){
	if(a>b)swap(a,b);
	ll ans=0;
	for(int l=1,r;l<=a;l=r+1){
		r=min(a/(a/l),b/(b/l));
		ans+=(tot[r]-tot[l-1])*(a/l)*(b/l);
	}
	return ans;
}
int main(){
	int t;
get_mu();
	scanf("%d",&t);
	while(t--){
		scanf("%lld %lld %lld",&a,&b,&d);
		printf("%lld\n",solve(a/d,b/d));
	}
	return 0;
}

例题 2 2 2

YY的GCD

我们首先构建最基本函数 f ( x ) = ∑ x = 1 n ∑ y = 1 m [ ( x , y ) ∈ P ] f(x)=\displaystyle \sum_{x=1}^n\displaystyle \sum_{y=1}^m [(x,y)\in P] f(x)=x=1ny=1m[(x,y)P]

我们可以开始枚举一下 gcd ⁡ ( x , y ) \gcd (x,y) gcd(x,y),不妨设这个东西是 k k k

则式子变为 ∑ k ∈ P ∑ x = 1 n ∑ y = 1 m [ ( x , y ) = k ] \displaystyle \sum_{k\in P}\displaystyle \sum_{x=1}^n\displaystyle \sum_{y=1}^m [(x,y)=k] kPx=1ny=1m[(x,y)=k]

我们对这个式子同时除以 k k k,得到:

a n s = ∑ k ∈ P ∑ x = 1 ⌊ n k ⌋ ∑ y = 1 ⌊ m k ⌋ [ ( x , y ) = 1 ] ans=\displaystyle \sum_{k\in P}\displaystyle \sum_{x=1}^{\lfloor \frac n k \rfloor}\displaystyle \sum_{y=1}^{\lfloor \frac m k \rfloor} [(x,y)=1] ans=kPx=1kny=1km[(x,y)=1]

我们发现这个式子可以化为 ∑ k ∈ P ∑ x = 1 ⌊ n k ⌋ ∑ y = 1 ⌊ m k ⌋ ∑ d ∣ gcd ⁡ ( x , y ) μ ( d ) \displaystyle \sum_{k\in P}\displaystyle \sum_{x=1}^{\lfloor \frac n k \rfloor}\displaystyle \sum_{y=1}^{\lfloor \frac m k \rfloor} \displaystyle \sum_{d|\gcd(x,y)}\mu(d) kPx=1kny=1kmdgcd(x,y)μ(d)

我们现将 d d d 提出来,由于要想让 μ ( d ) \mu(d) μ(d) 有贡献,我们必须让其满足 d ∣ x d|x dx 并且 d ∣ y d|y dy, 然后我们就可以喜闻乐见的发现,中间的两个 ∑ \sum 都消失啦,分别变成了 ⌊ n k d ⌋ \lfloor \frac n {kd} \rfloor kdn ⌊ m k d ⌋ \lfloor \frac m {kd} \rfloor kdm

我们就可以得到下面这个还算整洁的式子:(不妨设 n ≤ m n\le m nm)$\displaystyle \sum_{k\in P}^{n} \displaystyle \sum_{d}^{\lfloor\frac n k\rfloor}\mu(d)\lfloor \frac n {kd} \rfloor\lfloor \frac m {kd} \rfloor $

我们是可以在 O ( n ) O(n) O(n) 时间复杂度内筛出 μ ( d ) \mu(d) μ(d)的,同时也可以筛出质数。

⌊ n k d ⌋ ⌊ m k d ⌋ \lfloor \frac n {kd} \rfloor\lfloor \frac m {kd} \rfloor kdnkdm 可以 O ( 1 ) O(1) O(1) 处理。

我们接下来只需要处理一下 ∑ d ⌊ n k ⌋ μ ( d ) \displaystyle \sum_{d}^{\lfloor\frac n k\rfloor}\mu(d) dknμ(d) 这一块就可以了。

我们在这里使用 数论分块(反正不是分块,其实这个可以点)进行 O ( n ) O(\sqrt n) O(n ) 的求解。

于是,我们就可以快乐地解出答案啦!

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e7+1000,M=1e7;
int t;
bool isprime[N];
int mu[N],prime[N],cnt,f[N];
ll tot[N],n,m;
void get_mu(){
	mu[1]=1;
	for(int i=2;i<=M;i++){
		if(!isprime[i])prime[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt&&prime[j]*i<=M;j++){
			isprime[prime[j]*i]=true;
			if(i%prime[j]==0)break;
			mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<=cnt;i++){
		for(int j=1;j*prime[i]<=M;j++){
			f[j*prime[i]]+=mu[j];
		}
	}
	for(int i=1;i<=M;i++)tot[i]=tot[i-1]+f[i];
}
ll solve(ll x,ll y){
	ll ans=0;
	if(x>y)swap(x,y);
	for(ll l=1,r;l<=x;l=r+1){
		r=min(x/(x/l),y/(y/l));
		ans+=(tot[r]-tot[l-1])*(x/l)*(y/l);
	}
	return ans;
}
int main(){
	get_mu();
	scanf("%d",&t);
	while(t--){
		scanf("%lld %lld",&n,&m);
		printf("%lld\n",solve(n,m));
	}
	return 0;
}

例题 3 3 3

这道题目让我们求出 ∑ i = 1 n ∑ j = i + 1 n gcd ⁡ ( i , j ) \displaystyle \sum_{i=1}^n\sum_{j=i+1}^n \gcd(i,j) i=1nj=i+1ngcd(i,j)

我们不会求 ∑ gcd ⁡ ( i , j ) \sum \gcd(i,j) gcd(i,j),只会求 ∑ [ g c d ( i , j ) = 1 ] \sum [gcd(i,j)=1] [gcd(i,j)=1]

gcd ⁡ ( i , j ) = d \gcd(i,j)=d gcd(i,j)=d

所以,我们将 d d d 提取出来,然后得到:

∑ d = 1 n d × ∑ i = 1 n ∑ j = i + 1 n [ gcd ⁡ ( i , j ) = = 1 ] \displaystyle \sum_{d=1}^n d\times\sum_{i=1}^n \sum_{j=i+1}^n[\gcd(i,j)==1] d=1nd×i=1nj=i+1n[gcd(i,j)==1]

我们又可以欣喜的化简出来啦:

∑ d = 1 n d × ∑ i = 1 n ∑ j = i + 1 n ∑ d ∣ gcd ⁡ ( i , j ) μ ( d ) \displaystyle \sum_{d=1}^n d\times\sum_{i=1}^n \sum_{j=i+1}^n\sum_{d|\gcd(i,j)}\mu(d) d=1nd×i=1nj=i+1ndgcd(i,j)μ(d)

然后还是像上面一样对这个式子进行化简,但是,我们还是要重新减去一些:

∑ d = 1 n d × ∑ k = 1 ⌊ n d ⌋ μ ( k ) ⌊ n d ⌋ ⌊ n d ⌋ \displaystyle \sum_{d=1}^n d\times\sum_{k=1}^{\lfloor\frac n d\rfloor}\mu(k)\lfloor\frac n d\rfloor\lfloor\frac n d\rfloor d=1nd×k=1dnμ(k)dndn

我们还是可以预处理 μ ( k ) \mu(k) μ(k) 的前缀和,然后用数论分块 O ( n d ) O(\sqrt{\displaystyle \frac n d}) O(dn ) 的时间复杂度处理每一次关于 d d d 的查询。

于是代码可以如下:

#include<bits/stdc++.h>
using namespace std;
const int N=5e6+1000,M=5e6;
#define ll long long
ll tot[N],ans;
bool flag[N];
int mu[N],prime[N],cnt;
void get_mu(){
	mu[1]=1;
	for(int i=2;i<=M;i++){
		if(!flag[i])mu[i]=-1,prime[++cnt]=i;
		for(int j=1;j<=cnt&&prime[j]*i<=M;j++){
			flag[prime[j]*i]=true;
			if(i%prime[j]==0)break;
			mu[i*prime[j]]=-mu[i];
		}
	}
	for(int i=1;i<=M;i++)tot[i]=tot[i-1]+mu[i];
	return;
}
ll solve(ll a,ll b){
	ll ans=0;
	a/=b;
	for(int l=1,r;l<=a;l=r+1){
		r=min(a/(a/l),a);
		ans+=(tot[r]-tot[l-1])*(a/l)*(a/l);
	}
	return ans;
}
int main(){
	get_mu();
	ll n;
	cin>>n;
	for(int i=1;i<=n;i++){
		ans+=(i*solve(n,i));
	}
	ans-=(n*(n+1)/2);
	ans/=2;
	cout<<ans<<'\n';
	return 0;
}

例题 4 4 4

P2522-Problem b

我们可以根据前面的经验,可以知道:

∑ i = 1 a ∑ j = 1 b [ gcd ⁡ ( i , j ) = k ] \displaystyle \sum_{i=1}^{a}\sum_{j=1}^{b}[\gcd(i,j)=k] i=1aj=1b[gcd(i,j)=k] 可以化简为:

a n s = ∑ k a μ ( k ) ⌊ a k d ⌋ ⌊ b k d ⌋ ans=\displaystyle \sum_{k}^a \mu(k)\lfloor \frac {a} {kd}\rfloor\lfloor \frac {b} {kd}\rfloor ans=kaμ(k)kdakdb

我们接着思考如何计算:

我们发现,可以使用容斥原理来计算这个问题的答案,所以我们可以写出这个简单易懂的代码。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+1000;
const int M=1e6;
int t;
ll a,b,c,d,k;
ll tot[N];
int mu[N],prime[N],cnt;
bool flag[N];
void get_mu(){
	mu[1]=1;
	for(int i=2;i<=M;i++){
		if(!flag[i])prime[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt&&prime[j]*i<=M;j++){
			flag[i*prime[j]]=true;
			if(i%prime[j]==0)break;
			mu[i*prime[j]]=-mu[i]; 
		}
	}
	for(int i=1;i<=M;i++)tot[i]=tot[i-1]+mu[i];
	return; 
}
ll solve(ll a,ll b){
	ll ans=0;
	a/=k;b/=k;
	if(a>b)swap(a,b);
	for(ll l=1,r=0;l<=a;l=r+1){
		r=min(a/(a/l),b/(b/l));
		ans+=(tot[r]-tot[l-1])*(a/l)*(b/l);
	}
	return ans;
}
int main(){
	get_mu();
	scanf("%d",&t);
	while(t--){
		scanf("%lld %lld %lld %lld %lld",&a,&b,&c,&d,&k);
		printf("%lld\n",solve(b,d)-solve(a-1,d)-solve(b,c-1)+solve(a-1,c-1));
	}
	return 0;
}

这就是基础的莫比乌斯反演了。

希望大家可以继续学习。

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值