数论函数/莫比乌斯反演
1.1积性函数
数论函数:可以认为是定义在整数上的函数。
1)积性函数定义
(a,b) = 1,f(a,b) = f(a)f(b)
2)积性函数性质
-
对于积性函数
,是被所有 处的值决定的,即积性函数的值完全由素数的幂次决定a = 1,f(b) = f(1)f(b)
对于
- (重要!)积性函数
积性函数还是积性函数
1.2 、完全积性函数
1)定义
f(a,b) = f(a)f(b) 不要求(a,b) = 1
1.3常见积性函数
①
②常数函数
③单位函数
④欧拉函数
⑤因子函数
⑥
⑦
1.4 莫比乌斯函数
来看一个特殊的函数:常数函数
1. 莫比乌斯函数定义
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|
1 | -1 | -1 | 0 | -1 | 1 | -1 | 0 | 0 | 1 |
2.莫比乌斯函数性质
即 :
人话:一个数
该性质是莫比乌斯反演中最重要的技巧之一。
-
结合
1.5线性筛求积性函数
线性筛:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn =1000010; bool is_pri[maxn]; int pri[maxn]; int cnt; void init(int n) { memset(is_pri, true, sizeof(is_pri)); is_pri[1] = false; cnt = 0; for (int i = 2; i <= n; i++) { if (is_pri[i]) pri[++cnt] = i; for (int j = 1; j <= cnt && i * pri[j] <= n; j++) { is_pri[i * pri[j]] = false; if (i % pri[j] == 0)break; } } }
由于一个正整数(除
根据积性函数定义,对于一个积性函数
也就是说我们可以根据质因子推导出由该因子组成的合数。
对于一个合数有:
我们可以用线性筛帮助我们在
定义:
-
求出
. -
若
说明是合数,我们直接递推若
刚好是素数幂次,一般能从 推出
因子个数:
因子和:
欧拉函数:
莫比乌斯函数:
void init(int n)//求各个函数的最小素数因子和指数 { p[1] = 1; for(int i = 2;i<=n;i++) { if(!p[i])p[i] = i,pe[i] = i,pr[++cnt] = i; for(int j = 1;j<=cnt&&pr[j]*i<=n;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j])//i和i*pr[j]的最小的素因子是一样的 { //比如pe[5^3*7] = pr[5^2*7]*5 pe[i*pr[j]] = pe[i]*pr[j]; break; } else//说明被更小的质因子筛到了 { pe[i*pr[j]] = pr[j]; } } } }
//写法1 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn =1000010; int p[maxn],pr[maxn/5],pe[maxn]; int cnt; void init(int n)//求各个函数的最小素数因子和指数 { p[1] = 1; for(int i = 2;i<=n;i++) { if(!p[i])p[i] = i,pe[i] = i,pr[++cnt] = i; for(int j = 1;j<=cnt&&pr[j]*i<=n;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j])//i和i*pr[j]的素因子是一样的 { pe[i*pr[j]] = pe[i]*pr[j]; break; } else { pe[i*pr[j]] = pr[j]; } } } } uint f[N],a,b,ans; void compute(int n,function<void(int)>calcpe){ ans = 0; f[1] = 1; for(int i = 2;i<=n;i++) { if(i==pr[i])calcpe(i); else f[i] = f[pe[i]]*f[i/pe[i]];//积性函数,把这两个乘起来 } for(uint i = 1;i<=n;i++) ans ^= (a*i*f[i]+b); cout<<ans<<endl; } int main() { cin>>n>>a>>b; init(n); //因子个数 ans = 0; f[1] = 1; for(int i = 2;i<=n;i++) { if(i==pe[i]) f[i] = f[i/p[i]]+1;//i/p[i]==>p^{e-1} else f[i] = f[pe[i]]*f[i/pe[i]];//积性函数,把这两个乘起来 } for(uint i = 1;i<=n;i++) ans ^= (a*i*f[i]+b); cout<<ans<<endl; //因子和 ans = 0; f[1] = 1; for(int i = 2;i<=n;i++) { if(i==pe[i])f[i] = f[i/p[i]]+i; else f[i] = f[pe[i]]*f[i/pe[i]]; } for(uint i = 1;i<=n;i++) ans ^= (a*i*f[i]+b); cout<<ans<<endl; //欧拉函数 ans = 0; f[1] = 1; for(int i = 2;i<=n;i++) { if(i==pe[i])f[i] = i/p[i]*(p[i]-1); else f[i] = f[pe[i]]*f[i/pe[i]]; } for(uint i = 1;i<=n;i++) ans ^= (a*i*f[i]+b); cout<<ans<<endl; //莫比乌斯函数 ans = 0; f[1] = 1; for(int i = 2;i<=n;i++) { if(i==pe[i]){ if(i==p[i])f[i] = (uint)(-1); else f[i] = 0; }else f[i] = f[pe[i]]*f[i/pe[i]]; } for(uint i = 1;i<=n;i++) ans ^= (a*i*f[i]+b); cout<<ans<<endl; return 0; }
//写法2 #include<bits/stdc++.h> using namespace std; const int maxn =20010000; int p[maxn],pr[maxn/5],pe[maxn]; int cnt; void init(int n)//求各个函数的最小素数因子和指数 { p[1] = 1; for(int i = 2;i<=n;i++) { if(!p[i])p[i] = i,pe[i] = i,pr[++cnt] = i; for(int j = 1;j<=cnt&&pr[j]*i<=n;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j])//i和i*pr[j]的素因子是一样的 { pe[i*pr[j]] = pe[i]*pr[j]; break; } else { pe[i*pr[j]] = pr[j]; } } } } //如果单求phi int phi[maxn]; void phi(int n) { p[1] = 1; for(int i = 2;i<=n;i++) { if(!p[i])p[i] = i,phi[i] = i-1,pr[++cnt] = i; for(int j = 1;j<=cnt&&i*pr[j]<=n;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j]) { phi[i*pr[j]] = phi[i]*pr[j]; break; }else{ phi[i*pr[j]] = phi[i]*(pr[j]-1); } } } } unsigned int f[maxn],a,b,ans,n; void compute(int n,function<void(int)>calcpe){ ans = 0; f[1] = 1; for(int i = 2;i<=n;i++) { if(i==pe[i])calcpe(i); else f[i] = f[pe[i]]*f[i/pe[i]];//积性函数,把这两个乘起来 } for(int i = 1;i<=n;i++) ans ^= (a*(unsigned int)i*f[i]+b); cout<<ans<<endl; } int main() { cin>>n>>a>>b; init(n); //因子个数 compute(n,[&](int x){ f[x] = f[x/p[x]] + 1; }); //因子和 compute(n,[&](int x){ f[x] = f[x/p[x]] + x; }); //欧拉函数 compute(n,[&](int x){ f[x] = x/p[x]*(p[x]-1); }); //莫比乌斯函数 compute(n,[&](int x){ f[x] = x==p[x]?-1:0; }); return 0; }
2.1迪利克雷卷积
2.1.1定义
迪利克雷卷积是计算求和问题的有用工具。
设
定义为:
卷积有什么含义呢?
给一个特别的栗子:定义恒等函数
它们的卷积是:
所以记:
✳常见积性函数
下面积性函数,在迪利克雷卷积中常常用到:
✳迪利克雷卷积函数
将上述数论函数两两做卷积,可以得到一些新的数论函数:
常见的例子
-
❗
即
,莫比乌斯函数性质 -
❗
即
,即一个数所有约数的欧拉函数之和为这个数 -
根据除数函数定义:
除数函数与幂函数
欧拉函数与恒等函数
2.1.2性质
- 符合交换律和结合律
- (重要)
和 是积性函数=> 是积性函数
3.1莫比乌斯反演
引入
1. 莫比乌斯函数:
❗它有一个与欧拉函数
(补充:欧拉函数这个性质的证明:
若
那么对于正整数
对于素数幂次,由(1)可知
对于
由唯一分解定理可知
)
如下:
❗莫比乌斯反演Th1
莫比乌斯函数的和函数在整数
证明:
(1)对于
(2)对于
因为当
2.莫比乌斯反演
- 大致过程:给定一个求和柿子,对求和柿子进行变量替换、反演(交换求和顺序)等运算,最终化简为一个能在题目给定的时间范围内求解出的柿子。
- 核心技巧:
3.1.1形式
3.1.2莫比乌斯反演公式
❗莫比乌斯反演Th2
莫比乌斯反演公式。
若
约数的莫比乌斯反演:
若:
则:
倍数的莫比乌斯反演:
若:
则:
❗莫比乌斯反演Th3
设
莫比乌斯反演不需要
3.1.3构造
典例:求
不妨记
任何让我们构造一个
那么
即:
写成公式形式就是:
接下来问题就是如何求
eg.莫比乌斯反演
题意:
给定一个函数
思路:
#include<bits/stdc++.h> using namespace std; const int N = 1E6+10; unsigned int A,B,C; int n,cnt,f[N],p[N],mu[N],pr[N],g[N]; inline unsigned int rng61() { A ^= A << 16; A ^= A >> 5; A ^= A << 1; unsigned int t = A; A = B; B = C; C ^= t ^ A; return C; } int main(){ scanf("%d%u%u%u", &n, &A, &B, &C); for (int i = 1; i <= n; i++) f[i] = rng61(); p[1] = 1,mu[1] = 1; for(int i = 2;i<=n;i++) { if(!p[i])p[i] = i,mu[i] = (unsigned int)-1,pr[++cnt] = i; for(int j = 1;j<=cnt&&pr[j]*i<=n;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j]) { mu[i*pr[j]] = 0; break; } else mu[i*pr[j]] = (unsigned int)-mu[i]; } } //g = f*mu //g[n] = sum_{d|n} f[d]*mu[n/d] //g[n] = sum_{n = d1*d2} f[d1]*mu[d2] for(int d1 = 1;d1<=n;d1++) for(int d2 = 1;d1*d2<=n;d2++) g[d1*d2] += f[d1]*mu[d2]; unsigned int ans = 0; for(int i = 1;i<=n;i++)ans^=g[i]; cout<<ans<<endl; }
法二:
eg2互质数对
题意:
思路:
根据迪利克雷卷积
所以
根据
我们把
后面这一块用整数分块
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 10100000, M = 10000000; int p[N],pr[N/5],n,cnt; int mu[N],smu[N]; int main() { p[1] = 1,mu[1] = 1; for(int i = 2;i<=M;i++) { if(!p[i])p[i] = i,mu[i] = -1,pr[++cnt] = i; for(int j = 1;j<=cnt&&i*pr[j]<=M;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j]) { mu[i*pr[j]] = 0; break; }else{ mu[i*pr[j]] = -mu[i]; } } } for(int i = 1;i<=M;i++) smu[i] = smu[i-1]+mu[i]; int T; cin>>T; while(T--) { int n,m; cin>>n>>m; if(n>m)swap(n,m); ll ans = 0; //for(i~n)因为如果for(1~m)的话,n/l就已经是0了 for(int l = 1;l<=n;l++) { int r = min(n/(n/l),m/(m/l));//在[l,r]里面n/l和m/l都是不变的 ans += 1ll*(smu[r]-smu[l-1])*(n/l)*(m/l); l = r; } cout<<ans<<endl; } return 0; }
变式
如果题目改成求
题解:现在需要求的是
变形一下可得:
eg3.gcd之和
题意:
T组询问,每次给两个数
思路:
用狄利克雷卷积+提取公因数+整除分块化简
所以:
更一般地:如果
for (int l = 1, r; l <= min(n, m); l = r + 1) { r = min(n / (n / l), m / (m / l)); ans += (sum[r] - sum[l - 1]) * (n / l) * (m / l); }
本题代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 10100000, M = 10000000; ll p[N],pr[N/5],n,cnt; ll phi[N],sphi[N]; int main() { p[1] = 1,phi[1] = 1; for(int i = 2;i<=M;i++) { if(!p[i])p[i] = i,phi[i] = i-1,pr[++cnt] = i; for(int j = 1;j<=cnt&&i*pr[j]<=M;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j]) { phi[i*pr[j]] = phi[i]*pr[j]; break; }else{ phi[i*pr[j]] = phi[i]*(pr[j]-1); } } } for(int i = 1;i<=M;i++) sphi[i] = sphi[i-1]+phi[i]; int T; cin>>T; while(T--) { int n,m; cin>>n>>m; if(n>m)swap(n,m); ll ans = 0; //for(i~n)因为如果for(1~m)的话,n/l就已经是0了不用算了 for(int l = 1;l<=n;l++) { int r = min(n/(n/l),m/(m/l));//在[l,r]里面n/l和m/l都是不变的 ans += 1ll*(sphi[r]-sphi[l-1])*(n/l)*(m/l); l = r; } cout<<ans<<endl; } return 0; }
eg4.互质集合
题意:
思路:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll mod = 1e9+7; const int N = 10100000, M = 10000000; ll p[N],pr[N/5],n,cnt; ll mu[N],smu[N],pw[N]; int main() { std::ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr); p[1] = 1,mu[1] = 1; for(int i = 2;i<=M;i++) { if(!p[i])p[i] = i,mu[i] = -1,pr[++cnt] = i; for(int j = 1;j<=cnt&&i*pr[j]<=M;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j]) { mu[i*pr[j]] = 0; break; }else{ mu[i*pr[j]] = -mu[i]; } } } for(int i = 1;i<=M;i++) smu[i] = smu[i-1]+mu[i]; pw[0] = 1;//预处理2的幂次 for(int i = 1;i<=M;i++) pw[i] = pw[i-1]*2%mod ; int T; cin>>T; while(T--) { int n; cin>>n; ll ans = 0; for(int l = 1;l<=n;l++) { ll r = n/(n/l); ans += 1ll*(smu[r]-smu[l-1])*(pw[n/l]-1)%mod; ans%=mod; l = r; } if(ans<0)ans+=mod; cout<<ans<<'\n'; } return 0; }
eg5.LCMSUM1
题意:
思路:
令
因为
$ = n\sum_{d|n}g(d)\dfrac{\dfrac{n}{d}(\dfrac{n}{d}+1)}{2}$
(补充:
又因为我们知道,
那么
因为是分数,我们给它配一个
令
即:
于是:
综上所述:
#include<bits/stdc++.h> using namespace std; typedef unsigned long long u64; const int N = 10010000,M = 10000000; int p[N],pr[N/5],n,pe[N],cnt; u64 h[N]; void init(int n) { p[1] = 1; for(int i = 2;i<=n;i++) { if(!p[i])p[i] = i,pe[i] = i,pr[++cnt] = i; for(int j = 1;j<=cnt&&i*pr[j]<=n;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j]) { pe[i*pr[j]] = pe[i]*pr[j]; break; } else { pe[i*pr[j]] = pr[j]; } } } } int main() { init(M); h[1] = 1; for(int i = 2;i<=M;i++) { if(i==pe[i])h[i] = h[i/p[i]]+(u64)i*(i-i/p[i]); else h[i] = h[pe[i]]*h[i/pe[i]]; } int T; cin>>T; for(int i =1;i<=T;i++) { int n; cin>>n; u64 ans = n*(1+h[n])/2; cout<<ans%(1ull<<60)<<endl; } return 0; }
eg6.LCMSUM2
题意:
思路:
令
那么:
$ = \sum_{i = 1}^{n}\sum_{j = 1}^{m} ijf((i,j))$
于是:
写法一,先求素数幂次:
#include<bits/stdc++.h> using namespace std; typedef unsigned long long u64; const int N =10000010,M = 10000000; int p[N],pr[N/5],pe[N]; int cnt; int f[N],a,b,ans; u64 h[N]; void init(int n)//求各个函数的最小素数因子和指数 { p[1] = 1; for(int i = 2;i<=n;i++) { if(!p[i])p[i] = i,pe[i] = i,pr[++cnt] = i; for(int j = 1;j<=cnt&&pr[j]*i<=n;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j])//i和i*pr[j]的素因子是一样的 { pe[i*pr[j]] = pe[i]*pr[j]; break; } else { pe[i*pr[j]] = pr[j]; } } } } int main() { init(M); h[1] = 1; for(int i = 2;i<=M;i++) { if(i==pe[i])h[i] = i-(u64)i*p[i];//h(p^e) = p^e(1-p) else h[i] = h[pe[i]]*h[i/pe[i]];//合数,递推 } for(int i = 1;i<=M;i++)h[i] += h[i-1]; int T; cin>>T; while(T--) { int n,m; cin>>n>>m; if(n>m)swap(n,m); u64 ans = 0; for(int l = 1;l<=n;l++) { int d1 = n/l,d2 = m/l; int r = min(n/d1,m/d2); ans += (h[r]-h[l-1])*d1*(d1+1)*d2*(d2+1)/4; l = r; } cout<<ans%(1ull<<60)<<endl; } return 0; }
写法二:
#include<bits/stdc++.h> using namespace std; typedef unsigned long long u64; const int N =10000010,M = 10000000; int p[N],pr[N/5]; int cnt; int f[N],a,b,ans; u64 h[N]; int main() { h[1] = 1; p[1] = 1; for(int i = 2;i<=M;i++) { if(!p[i])p[i] = i,h[i] = i*(u64)(1-i),pr[++cnt] = i; for(int j = 1;j<=cnt&&pr[j]*i<=M;j++) { p[i*pr[j]] = pr[j]; if(p[i]==pr[j]) { h[i*pr[j]] = h[i]*pr[j]; break; } else { h[i*pr[j]] = h[i]*pr[j]*(1-pr[j]); } } } for(int i = 1;i<=M;i++)h[i] += h[i-1]; int T; cin>>T; while(T--) { int n,m; cin>>n>>m; if(n>m)swap(n,m); u64 ans = 0; for(int l = 1;l<=n;l++) { int d1 = n/l,d2 = m/l; int r = min(n/d1,m/d2); ans += (h[r]-h[l-1])*d1*(d1+1)*d2*(d2+1)/4; l = r; } cout<<ans%(1ull<<60)<<endl; } return 0; }
✳总结做题技巧
根据我们的基本结论:有
我们发现
我们发现
所以有:
最终问题转化为求解:
这个式子我们可以通过整除分块在
核心:
-
整除与循环枚举,循环枚举1,2,⋯,n,循环内的式子每固定长度才会取到,其余均为0,则可以直接缩循环区间。
比如:求解
我们换个角度考虑:显然
至少含有 这个因子,才可能产生贡献。也就是每 会包含一个 的倍数每段至多
个 的倍数,我们将 每段缩短为长度为 ,那么原本长度为 就被缩短为 ,当然最后一个区间可能到达不了完整 的倍数。缩完区间之后,选择
相当于从原本的区间 中选择了 ,那么在缩完区间之后,任从 和 中选出一个数,映射回去原本的区间就都包含 ,也就变成了选出的区间编号需要互质所以有:
核心点:内外两层循环,每隔长度
才会出现一个有用的点,以此来缩短循环区间。 -
将只含有
的式子,转化为含有 的式子比如:求解
,其中如何将这类不含有
的形式的柿子转化为 有关呢?方法:在外层循环多枚举一层最大公约数
,内层循环利用技巧 缩范围,枚举变量 。 -
外循环变量为
,内循环变量为 ,式子中出现二者乘积 。变形为外循环变量 ,内循环枚举约数为 。在3中我们得到
变形结果为接下来对
进行反演-
在上式中,外层循环的变量为
,内层循环的变量为 ,二者的乘积为 ,设 -
在莫反的题目中,式子中出现内外两层循环的变量,如
,且 仍然处于 ,我们此时将原式变形,外层循环枚举变量 ,内层循环枚举其约数 ,就容易构造出狄利克雷卷积的形式于是我们就构造出了内循环为狄利克雷卷积
设
即
所以原柿等于:
-
-
对于所有带
的,我们有一般求解通式对于以上我们都能找到数论函数
满足 ,即我们把
提出来其中
显然:
以上方法对所有带
的均适用但这个通式的限制性过强,内层式子必须只和
有关,即只能是一个一元函数,故使用范围有限 -
将
和 转化将
和 转化:在竞赛中,莫比乌斯反演常常涉及到数论题中两个常用的小技巧:提取公因数,整除分块。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具