莫比乌斯反演学习笔记
引入
对于数论问题中的一些函数 ,如果很难直接求出它的值,却容易求出其倍数和或约数和 ,那么可以通过莫比乌斯反演化简运算,求得 的值。
定义
为莫比乌斯函数,定义如下:
令 ,其中 为质因子,。上述定义表示:
1.当 时,;
2.当 时:
-
当存在 ,使得 时,,也就是只要有某个质因子出现次数超过一次, 就等于 。
-
当任意 ,都满足 时,, 即为出现的质因子的总个数。
代码实现
根据定义, 可以在线性筛的过程中同时得到,时间复杂度为 :
mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i]) prime[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&i*prime[j]<=n;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
性质
证明
令
那么
根据二项式定理,只有当 即 时原式值为 ,反之则为 。
结论
这个等式的证明比较显然,根据前面提到的性质,只有当 时,才满足,反之 。
莫比乌斯变换
形式一
形式二
「HAOI 2011」Problem b
题意
组询问,每次询问求值:
。
思路
根据差分的思路,可以将每次询问拆成四个相同形式的式子:
化简该式子:
利用莫比乌斯函数的性质,进一步化简,得到:
变换求和顺序,先枚举 可得:
而 中, 的倍数有 个,故原式化为:
于是这个式子就可以用数论分块求解。时间复杂度为
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5e4+10;
int prime[N],mu[N],tot;bool vis[N];
void init()
{
mu[1]=1;
for(int i=2;i<N;i++)
{
if(!vis[i]) prime[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&prime[j]*i<N;j++)
{
vis[prime[j]*i]=true;
if(i%prime[j]==0) {mu[i*prime[j]]=0;break;}
mu[i*prime[j]]=-mu[i];
}
}
for(int i=2;i<N;i++) mu[i]+=mu[i-1];
}
int solve(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()
{
int T,n,m,x,y,k;scanf("%d",&T);init();
while(T--)
{
scanf("%d%d%d%d%d",&x,&n,&y,&m,&k);
printf("%d\n",solve(n/k,m/k)-solve((x-1)/k,m/k)-solve(n/k,(y-1)/k)+solve((x-1)/k,(y-1)/k));
}
return 0;
}
Crash的数字表格
求值:
对 20101009$ 取模。
。
思路
不难发现,原式等价于:
枚举 :
把 提到前面来,套用莫比乌斯函数:
单独计算后面两项的值,记
变换求和顺序,先枚举 可得:
令 ,带入式子可得:
注意到这个式子前半部分可以预处理前缀和用整除分块做,后半部分则可以直接计算,那么原式就是:
于是总的时间复杂度就为 。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e7+10,mod=20101009;
int mu[N],sum[N],prime[N],tot;bool vis[N];
void init()
{
mu[1]=1;
for(int i=2;i<N;i++)
{
if(!vis[i]) prime[++tot]=i,mu[i]=-1;
for(int j=1;j<=tot&&prime[j]*i<N;j++)
{
vis[prime[j]*i]=true;
if(i%prime[j]==0) {mu[i*prime[j]]=0;break;}
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<N;++i) sum[i]=(sum[i-1]+1ll*i*i%mod*(mu[i]+mod))%mod;
}
int Sum(int n,int m){return (1ll*n*(n+1)/2%mod)*(1ll*m*(m+1)/2%mod)%mod;}
int func(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=(res+1ll*(sum[r]-sum[l-1]+mod)*Sum(n/l,m/l)%mod)%mod;
}
return res;
}
int solve(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=(res+1ll*(r-l+1)*(l+r)/2%mod*func(n/l,m/l)%mod)%mod;
}
return res;
}
int main()
{
int n,m;scanf("%d%d",&n,&m);init();printf("%d\n",solve(n,m));
return 0;
}
约数个数和
组询问。每次询问给定 ,求:
其中 为 的约数个数。
思路:
首先需要用到 函数的一个特殊性质,即:
考虑将这个式子化简:
带回原式,得:
对于 函数,也可以在线性筛的同时求出。那么就可以做到 预处理, 询问。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5e4+10;
#define LL long long
LL d[N];int t[N],mu[N],prime[N],tot;bool vis[N];//t表示最小质因子的出现次数
void init()
{
mu[1]=d[1]=1;
for(int i=2;i<N;i++)
{
if(!vis[i]) prime[++tot]=i,d[i]=2,mu[i]=-1,t[i]=1;
for(int j=1;j<=tot&&prime[j]*i<N;j++)
{
vis[prime[j]*i]=true;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
d[i*prime[j]]=d[i]/(t[i]+1)*(t[i]+2);
t[i*prime[j]]=t[i]+1;
break;
}
mu[i*prime[j]]=-mu[i];
d[i*prime[j]]=d[i]*2;
t[i*prime[j]]=1;
}
}
for(int i=1;i<N;i++) mu[i]+=mu[i-1],d[i]+=d[i-1];
}
LL solve(int n,int m)
{
LL 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])*d[n/l]*d[m/l];
}
return res;
}
int main()
{
int T,n,m;scanf("%d",&T);init();
while(T--) scanf("%d%d",&n,&m),printf("%lld\n",solve(n,m));
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效