莫比乌斯反演专题(基础篇)
0:前置知识
线性筛积性函数
int mu[N],prime[N],tot=0;
int v[N];
void sieve()
{
mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
mu[i]=-1;
}
for(int j=1;j<=tot;j++)
{
if(prime[j]>v[i]||i*prime[j]>n) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else mu[i*prime[j]]=mu[i]*mu[prime[j]];
}
}
}
整除分块
int l,r;
int ans=0;
for(;l<=r;l=r+1)
{
r=(n/(n/l));
ans+=(r-l+1)*(n/l);
}
莫比乌斯函数
记\(\mu (n)\)为\(n\)的莫比乌斯函数,其中$$n=\Pi_{p_i}^{c_i}$$
则若\(c_i\)不等于1,\(\mu(n)=0\)
若\(p_i\)的个数为\(k\),则\(\mu(n)=(-1)^k\)
特殊的,\(\mu(1)=1\)
性质:\(\sum_{d|n}\mu(d)=[n==1]\)
\(\mu(n)=-\sum_{d|n ,d!=n}\mu(d))\)
\(\mu(n)=\mu(a)\mu(b)\),其中\(n=a\times b\),且\(gcd(a,b)=1\)
狄雷克雷卷积
则\(h\)为\(g\)和\(f\)的狄利克雷卷积,记作\(h=f\times g\)
记\(e(n)\)为元函数,表示\(e(n)=[n==1]\)
记\(id(n)\)为单位函数,表示\(id(n)=n\)
记\(I(n)\)为恒等函数,表示\(I(n)=1\)
几个性质
1:\(f\times g=g\times f\):交换律
2:\(f\times g\times h=f\times (g\times h)\)
3:\(f\times e=f\):因为如果\(n/d\)不等于1,式子为0,而此时式子的值为\(f(n)\)
4:\(\mu \times I=e\):\(\sum_{d|n}\mu(d)=[n==1]\)
5:\(\varphi \times I= id\):\(\sum_{d|n}\varphi(d)=n\)
6:$$\varphi=\mu\times id$$
证明:将5式的两边同时卷上\(\mu\),由之前的性质可得,\(\varphi =\mu \times id\)
莫比乌斯反演
反演公式:记\(f(n)=\sum_{d|n}g(n)\)
则\(g(n)=\sum_{d|n}f(d)\mu(n/d)\)
原理:$$f = g\times I$$
例题
1:周期性的字符串
对于长度为n、由小写字母组成的(不一定要用所有字母)字符串s,如果存在一个字符串t,t≠s,使得s可以由t重复若干次恰好得到(不能有多余字符),那么我们称s具有周期性。
周期性字符串计数问题是指对于给定的n,求有多少个长度为n的周期性字符串。答案比较大,只需要输出对\(10^9+7\)取余即可。
注意串中只包含26个小写字母。
分析
设\(f(n)\)表示长度为\(n\)的字符串个数之和,则\(f(n)=26^n\)
设\(g(n)\)表示长度为\(n\),且没有周期性为(或者说周期为1)的字符串个数
则长度为n的字符串可以看成长度为d的字符串重复出现\(n/d\)次形成的
即\(f(n)=\sum_{d|n}g(n)\)
由莫比乌斯反演公式可知\(g(n)=\sum_{d|n}f(d)\mu(n/d)\)
我们求出\(g(n)\),则题目所求的就是周期不为1的字符串个数,我们用总的字符串数量减掉周期为1,长度为n的字符串就是题目所求
也就是\(f(n)-g(n)\)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9+7;
const int N = 1e6+7;
int mu[N],prime[N],tot=0;
int v[N];
LL Pow[N];
void init(int n)
{
mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
mu[i]=-1;
}
for(int j=1;j<=tot;j++)
{
if(prime[j]>v[i]||i*prime[j]>n) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else mu[i*prime[j]]=mu[i]*mu[prime[j]];
}
}
Pow[0]=1;
for(int i=1;i<=n;i++)
Pow[i]=1ll*Pow[i-1]*26%mod;
}
LL f(int n)
{
return Pow[n];
}
LL g(int n)
{
LL ans=0;
for(int d=1;d<=n;d++)
if(n%d==0) ans=(ans+f(d)*mu[n/d]%mod)%mod;
return ans;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
LL n;
cin>>n;
init(n);
cout<<((f(n)-g(n))%mod+mod)%mod;
return 0;
}
2:互质数对
求\(\sum_{i=1}^n\sum_{j=1}^n[gcd(i,j)==1]\)
因为\(\sum_{d|n}\mu(d)=[n==1]\)
把\(n\)换成\(gcd(i,j)\)可知,所求为\(\sum_{i=1}^n\sum_{j=1}^n\sum_{d|i,d|j}\mu(d)\)
将\(d\)提前,\(\sum_{d=1}^n\mu(d)\sum_{d|i}\sum_{d|j}1\)
把\(i,j\)换成倍数可得
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e7+7;
LL mu[N];
int prime[N],tot=0;
int v[N];
void init(int n)
{
mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
mu[i]=-1;
}
for(int j=1;j<=tot;j++)
{
if(prime[j]>v[i]||i*prime[j]>n) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
else mu[i*prime[j]]=mu[i]*mu[prime[j]];
}
}
for(int i=1;i<=n;i++)
mu[i]=mu[i-1]+mu[i];
}
int main()
{
freopen("mu.in","r",stdin);
freopen("mu.out","w",stdout);
int n;
cin>>n;
init(n);
LL ans=0;
int l=1,r;
for(;l<=n;l=r+1)
{
r=(n/(n/l));
ans=(ans+(mu[r]-mu[l-1])*(n/l)*(n/l));
}
cout<<ans;
return 0;
}
【BZOJ2693】jzptab
求
思路
记\(S(n)=\sum_{i=1}^ni\)
记\(f(n)=n\sum_{d|n}\mu(d)d\)
则
根据整除分块的相关知识
\(\lfloor \frac{n}{T} \rfloor\)的变化量只有\(O(\sqrt n)\)种,所以\(S(\lfloor \frac{n}{T} \rfloor)\)的变化量也有\(O(\sqrt n)\)种,处理剩余部分的前缀和,时间复杂度\(O(n+T\sqrt n)\),其中\(T\)为询问次数
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1e8+9;
const LL N = 1e7+7;
LL f[N];
LL v[N],prime[N],tot=0;
LL mu[N];
void init(LL n)
{
f[1]=1;
mu[1]=1;
for(LL i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
mu[i]=-1;
f[i]=1+(-1)*i;
f[i]=(f[i]+mod)%mod;
}
for(LL j=1;j<=tot;j++)
{
if(prime[j]>v[i]||i*prime[j]>n) break;
v[prime[j]*i]=prime[j];
if(i%prime[j]==0)
{
f[prime[j]*i]=f[i]; //因为质因子的指数加一,所以新增的因子的莫比乌斯函数之都为0,所以相当于没有加
break;
}
else f[i*prime[j]]=(f[i]+(-1)*f[i]*prime[j]%mod+mod)%mod;
//新增一个质因子,这个质因子可以和之前的所有因子组合,也就是f[i]*prime[j],但是长度加一,莫比乌斯函数值变成相反数,所以要乘-1
}
}
for(LL i=1;i<=n;i++)
f[i]=(f[i]*i%mod+f[i-1])%mod;
}
LL S(LL n)
{
return 1ll*(1+n)*n/2%mod;
}
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
init(1e7);
LL T;
cin>>T;
while(T--)
{
LL n,m;
scanf("%lld %lld",&n,&m);
if(n>m) swap(n,m);
LL l=1,r;
LL ans=0;
for(;l<=n;l=r+1)
{
r=min((n/(n/l)),m/(m/l));
ans=(ans+S(n/l)*S(m/l)%mod*(f[r]-f[l-1]+mod)%mod)%mod;
}
printf("%lld\n",ans);
}
return 0;
}
[NOI2010day1]T1-能量采集
化简题意可知要求的是
也就是
而我们知道\(n=\sum_{d|n}\varphi (d)\)
把\(gcd(i,j)\)带入\(n\)可得
预处理欧拉函数前缀和,再套上整除分块即可
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N = 1e5+7;
LL phi[N];
LL v[N],prime[N],tot=0;
void init(LL n)
{
phi[1]=1;
for(LL i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
phi[i]=i-1;
}
for(LL j=1;j<=tot;j++)
{
if(v[i]<prime[j]||i*prime[j]>n) break;
v[i*prime[j]]=prime[j];
if(v[i]%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
for(LL i=1;i<=n;i++)
phi[i]=phi[i]+phi[i-1];
}
int main()
{
freopen("energy.in","r",stdin);
freopen("energy.out","w",stdout);
init(1e5);
LL n,m;
cin>>n>>m;
if(n>m) swap(n,m);
LL ans=0;
LL l=1,r;
for(;l<=n;l=r+1)
{
r=min((n/(n/l)),(m/(m/l)));
ans=(ans+1ll*(phi[r]-phi[l-1])*1ll*(n/l)*(m/l));
}
cout<<2*ans-n*m;
return 0;
}
YY的GCD
求
化简:
设\(f(n)=\sum_{d|n,d\in prime }\mu(\frac{T}{d})\)
预处理\(f(n)\)的前缀和,这个可以枚举质数\(d\)和\(d\)的倍数\(n\)算出,因为只枚举质数,所以复杂度\(O(n\;logn\;logn)\)
接着整除分块即可
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e7+7;
int u[N];
int prime[N],top=0;
LL T[N];
LL sum[N];
bool vis[N];
void sieve(int n)
{
u[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
u[i]=-1;
prime[++top]=i;
}
for(int j=1;j<=top&&i*prime[j]<=n;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
u[i*prime[j]]=0;
break;
}
else u[i*prime[j]]=u[i]*u[prime[j]];
}
}
for(int j=1;j<=top;j++)
{
for(int i=1;i*prime[j]<=n;i++)
{
T[i*prime[j]]+=u[i];
}
}
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+T[i];
}
LL count(int n,int m)
{
LL res=0;
for(int l=1,r=0;l<=n;l=r+1)
{
r=min(n/(n/l),m/(m/l));
res=res+(sum[r]-sum[l-1])*(n/l)*(m/l);
}
return res;
}
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
int T;
cin>>T;
sieve(1e7);
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
if(n>m) swap(n,m);
printf("%lld\n",count(n,m));
}
return 0;
}
[SDOI2015] 约数个数和
求
其中\(d(n)\)表示\(n\)的约数个数和
首先有引理
证明详见这篇blog
根据引理,所求即为
设\(f(n)=\sum_{i=1}^n\lfloor \frac{n}{i}\rfloor\),\(N=\lfloor \frac{n}{d} \rfloor,M=\lfloor \frac{m}{d} \rfloor\)
这个形式就可以很舒服的整除分块了
\(f(n)\)可以整除分块\(O(n\sqrt n)\)预处理
总复杂度\(O(n\sqrt n+T\sqrt n)\),\(T\)是询问次数
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e4+100;
inline int read()
{
int X=0; bool flag=1; char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
if(flag) return X;
return ~(X-1);
}
LL prime[N],tot,u[N],sum[N],f[N];
bool vis[N];
void prework(int n)
{
u[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i])
{
prime[++tot]=i;
u[i]=-1;
}
for(int j=1;j<=tot&&prime[j]*i<=n;j++)
{
int p=prime[j];
vis[i*p]=1;
if(i%p==0) break;
else u[i*p]=u[i]*u[p];
}
}
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+u[i];
for(int i=1;i<=n;i++)
{
for(int l=1,r;l<=i;l=r+1)
{
r=(i/(i/l));
f[i]=f[i]+1ll*(r-l+1)*1ll*(i/l);
}
}
}
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
int t,n,m;
cin>>t;
prework(50000);
while(t--)
{
n=read();
m=read();
LL range=min(n,m);
LL ans=0;
for(int l=1,r;l<=range;l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans=ans+(sum[r]-sum[l-1])*1ll*f[n/l]*1ll*f[m/l];
}
printf("%lld\n",ans);
}
return 0;
}
[HAOI2011]Problem b
求\(\sum_{l\leq i \leq r}\sum_{a\leq j\leq b}[gcd(i,j)==k]\)
观察到这类似于二维前缀和,我们只需要求出这样一个式子就行了
令\(N=\lfloor \frac{n}{k} \rfloor\),\(M=\lfloor \frac{m}{k} \rfloor\)
直接整除分块即可
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
int u[N],vis[N];
int prime[N],top=0;
int sum[N];
void sieve(int n)
{
u[1]=1;
for(int i=2;i<=n;i++)
{
if(vis[i]==0)
{
prime[++top]=i;
u[i]=-1;
}
for(int j=1;j<=top&&prime[j]*i<=n;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
u[i*prime[j]]=0;
break;
}
else u[i*prime[j]]=u[i]*u[prime[j]];
}
}
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+u[i];
}
int count(int a,int b,int d)
{
a/=d;
b/=d;
int res=0;
if(a>b) swap(a,b);
for(int L=1,R=0;L<=a;L=R+1)
{
R=min(a/(a/L),b/(b/L));
res=res+(sum[R]-sum[L-1])*(a/L)*(b/L);
}
return res;
}
int main()
{
sieve(1e5+7);
int T;
cin>>T;
while(T--)
{
int a,b,c,d,k;
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
printf("%d\n",count(b,d,k)-count(b,c-1,k)-count(a-1,d,k)+count(a-1,c-1,k));
}
return 0;
}
BZOJ4407 于神之怒加强版
求
化简
注意因为枚举了倍数,所以k前的\(\sum\)就被提前了,不能算
设$$f(n)=\sum_{d|n}d^k\mu(\frac{n}{d})$$
那么\(f(n)\)是两个积性函数的狄利克雷卷积,所以\(f(n)\)是积性函数
直接线性筛晒一下就行了
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N =5e6+7;
const int mod = 1e9+7;
LL Pow(LL a,LL b)
{
LL res=1;
while(b)
{
if(b&1) res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
int v[N],prime[N],tot=0;
LL mu[N],pk[N],f[N];
int k;
void init(int n)
{
mu[1]=1;
f[1]=1;
for(int i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
pk[i]=Pow(i,k);
f[i]=(pk[i]-1+mod)%mod;
}
for(int j=1;j<=tot;j++)
{
if(prime[j]>v[i]||i*prime[j]>n) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
{
f[i*prime[j]]=1ll*f[i]*pk[prime[j]]%mod;
break;
}
else f[i*prime[j]]=1ll*f[i]*f[prime[j]];
}
}
for(int i=1;i<=n;i++)
f[i]=(f[i]+f[i-1])%mod;
}
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
int T;
cin>>T>>k;
init(5e6);
while(T--)
{
int n,m;
scanf("%d %d",&n,&m);
if(n>m) swap(n,m);
LL ans=0;
int l=1,r;
for(;l<=n;l=r+1)
{
r=min(n/(n/l),m/(m/l));
ans=(ans+(f[r]-f[l-1]+mod)%mod*(n/l)%mod*(m/l)%mod)%mod;
}
printf("%lld\n",ans);
}
return 0;
}
技巧总结
1:可以枚举要求的数,比如gcd,然后用布尔表达式写出来
2:将某些项提前,然后将和它相关的项用改成枚举倍数,使上指标方便进行整除分块
3:一般情况最终有两种情况:
一是可以直接写成整除分块的形式
二是整除下有两个字母的乘积,此时可以枚举乘积,并将乘积提前,此时其余的项是封闭的,且一般可以写成一个关于乘积的函数,可以用线性筛预处理出

浙公网安备 33010602011771号