有错请纠正,谢谢。
莫比乌斯函数
这个比较简单,前置知识 积性函数 积性函数 积性函数。
积型函数是满足 ∀ 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=1∏kai。
莫比乌斯函数为:
μ ( 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^+ ∀n∈N+,都有 ∑ d ∣ n μ ( d ) = [ n = 1 ] \displaystyle\sum_{d|n} \mu(d)=[n=1] d∣n∑μ(d)=[n=1]
②对于 ∀ n ∈ N + \forall n\in N^+ ∀n∈N+,都有 ∑ d ∣ n μ ( d ) d = φ ( n ) n \displaystyle \sum_{d|n} \displaystyle \frac {\mu(d)} d=\displaystyle \frac {\varphi(n)} n d∣n∑dμ(d)=nφ(n)
③对于 ∀ n ∈ P \forall n\in P ∀n∈P,都有 μ ( 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)=d∣n∑f(d)→f(n)=d∣n∑μ(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)=n∣d∑f(d)→f(n)=n∣d∑μ(nd)F(d)
例题 1 1 1
我们在说这道题目之前需要补充一个非常有名的恒等式。
[ 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]=d∣gcd(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=1∑aj=1∑b[gcd(i,j)=d]
我们可以先操作一步,将 gcd \gcd gcd 中的 d d d 提取出来,然后就可以化成这样一个式子:(不妨设 a ≤ b a\le b a≤b)
∑ 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=1∑⌊da⌋j=1∑⌊db⌋[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=1∑⌊da⌋j=1∑⌊db⌋k∣gcd(i,j)∑μ(k)
我们可以将 i → ⌊ i k ⌋ i \rightarrow \lfloor \displaystyle \frac {i}{k}\rfloor i→⌊ki⌋, j → ⌊ j k ⌋ j \rightarrow \lfloor \displaystyle \frac {j}{k}\rfloor j→⌊kj⌋,这样就可以避免去枚举 k ∣ gcd ( i , j ) k|\gcd(i,j) k∣gcd(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=1∑ai=1∑⌊kda⌋j=1∑⌊kdb⌋μ(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=1⌊kda⌋∑j=1⌊kdb⌋ 之外,就可以得到:
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=k∑aμ(k)⌊kda⌋⌊kdb⌋
我们发现 ∑ k a μ ( k ) \displaystyle \sum_{k}^a \mu(k) k∑aμ(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
我们首先构建最基本函数 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=1∑ny=1∑m[(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] k∈P∑x=1∑ny=1∑m[(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=k∈P∑x=1∑⌊kn⌋y=1∑⌊km⌋[(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) k∈P∑x=1∑⌊kn⌋y=1∑⌊km⌋d∣gcd(x,y)∑μ(d)
我们现将 d d d 提出来,由于要想让 μ ( d ) \mu(d) μ(d) 有贡献,我们必须让其满足 d ∣ x d|x d∣x 并且 d ∣ y d|y d∣y, 然后我们就可以喜闻乐见的发现,中间的两个 ∑ \sum ∑ 都消失啦,分别变成了 ⌊ n k d ⌋ \lfloor \frac n {kd} \rfloor ⌊kdn⌋ 和 ⌊ m k d ⌋ \lfloor \frac m {kd} \rfloor ⌊kdm⌋!
我们就可以得到下面这个还算整洁的式子:(不妨设 n ≤ m n\le m n≤m)$\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 ⌊kdn⌋⌊kdm⌋ 可以 O ( 1 ) O(1) O(1) 处理。
我们接下来只需要处理一下 ∑ d ⌊ n k ⌋ μ ( d ) \displaystyle \sum_{d}^{\lfloor\frac n k\rfloor}\mu(d) d∑⌊kn⌋μ(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=1∑nj=i+1∑ngcd(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=1∑nd×i=1∑nj=i+1∑n[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=1∑nd×i=1∑nj=i+1∑nd∣gcd(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=1∑nd×k=1∑⌊dn⌋μ(k)⌊dn⌋⌊dn⌋
我们还是可以预处理 μ ( 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
我们可以根据前面的经验,可以知道:
∑ i = 1 a ∑ j = 1 b [ gcd ( i , j ) = k ] \displaystyle \sum_{i=1}^{a}\sum_{j=1}^{b}[\gcd(i,j)=k] i=1∑aj=1∑b[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=k∑aμ(k)⌊kda⌋⌊kdb⌋
我们接着思考如何计算:
我们发现,可以使用容斥原理来计算这个问题的答案,所以我们可以写出这个简单易懂的代码。
#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;
}
这就是基础的莫比乌斯反演了。
希望大家可以继续学习。