莫比乌斯反演学习笔记
前置知识
目录#
一、莫比乌斯函数#
- 定义
- 性质
- 证明
二、莫比乌斯反演#
- 反演公式
- 形式
- 证明
- 形式
三、例题#
- POI2007 zap-Queries
- YY 的 GCD
- SDOI2014 数表
- BZOJ3309 DZY Loves Math
- SDOI2015 约数个数和
- SDOI2017 数字表格
- BZOJ4407 于神之怒加强版
- 国家集训队 Crash 的数字表格、JZPTAB
莫比乌斯函数#
定义#
设 ,其中 为 的质因子,。
性质#
莫比乌斯函数是积性函数,有以下性质
常用于莫比乌斯反演中。
证明#
- 当 时显然成立;
- 当 时,设
那么有
考虑我们选 个本质不同的质因子,有 种方法,其中每个数的莫比乌斯函数值为 ,所以这样计算出原式的贡献。
那么我们需要证明
考虑二项式定理
证毕。
莫比乌斯反演#
反演公式#
设 是定义在正整数集上的两个函数。
形式 #
若有
则有
证明#
考虑对下式进行变换
代入定义,有
可以交换求和号
详细解释
对于上面这一步变换,有以下考虑。由于 ,存在 满足 。那么对于每一个符合条件的 都有与之对应的 ,那么就可以转化为
考虑原式,相当于先枚举符合条件的 ,再枚举在此时 基础上符合条件的 ,我们发现,换成现在的式子后,对答案的统计仍然不重不漏。
还可以换一种方式考虑,对于原式,我们可以转化为
显然,对于两个限制条件,我们可以直接消去第一个条件,再将艾弗森括号转换进和式,得到
得到这个式子,我们就可以直接交换 和 的位置了,并可以将和式中的一个条件拆开成为两个和式
又由 可以得到
把艾弗森括号里的部分转化一下
只有当 时,该式子才有值,即
证毕。
形式 #
若有
则有
证明与形式 的证明类似,不再展开说了。
例题#
POI2007 ZAP-Queries#
题目大意#
给定 和 ,求
证明#
为了方便处理,我们钦定 。
首先考虑 同时是 和 的最大公约数,所以满足 且 。
于是我们可以把 提出来。
然后我们可以利用莫比乌斯函数的结论,变为
此时,实际上 是随意枚举的,只要保证 就可以。既然 不受到前面两个和式的限制,就可以向前提,先枚举 ,又考虑到满足 的最大的值也就是 ,所以 就枚举到这就行。
又考虑对 产生限制的只有 ,那么就可以把她也向前提前面去,放在 的和式后面。我们也可以顺便把其他和式的限制条件放在她后面。
然后我们可以发现,观察后面两个和式。例如第二个和式,满足 的 最多有 个。
那么我们这个式子又可以改变,变成下面这个样子。
对于后面两个向下取整的部分,我们可以整除分块求,前面的部分我们可以预处理求出莫比乌斯函数。
Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
#define int long long
const int N = 50050;
int T,n,m,d;
int pri[N],p[N],cnt;
int miu[N],mu[N];
void Ready() {
p[1] = 1;
mu[1] = 1;
for(int i = 2;i < N; i++) {
if(p[i] == 0) {
pri[++cnt] = i;
mu[i] = -1;
}
for(int j = 1;j <= cnt && i * pri[j] < N; j++) {
p[i * pri[j]] = 1;
if(i % pri[j] == 0)
break;
mu[i * pri[j]] = -mu[i];
}
}
for(int i = 1;i < N; i++)
miu[i] = mu[i] + miu[i - 1];
return ;
}
int ans = 0;
signed main() {
cin >> T;
Ready();
while(T--) {
cin >> n >> m >> d;
if(n > m)
swap(n,m);
n /= d;
m /= d;
int l = 1,r = 0;
while(l <= n) {
r = min(n / (n / l),m / (m / l));
ans += (n / l) * (m / l) * (miu[r] - miu[l - 1]);
l = r + 1;
}
cout << ans << "\n";
ans = 0;
}
return 0;
}
YY 的 GCD#
题目大意#
给定 和 ,求
即求最大公约数为质数的数对个数。
思路#
为了方便处理,我们还是钦定 。
我们可以枚举一个 ,把原式转化为
利用莫比乌斯函数性质,有
交换求和号,令 ,有
式子推到这里,如果我们去枚举 ,肯定会 TLE,我们令 ,有
我们发现,后面那部分可以预处理。
考虑每个质数 ,对于它的倍数 ,其值加上 。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 10050000;
int T,n,m;
bool p[N];
int mu[N],sum[N],f[N];
int prime[N],cnt;
void Seive() {
mu[1] = 1;
for(int i = 2;i <= 10000000; i++) {
if(!p[i]) {
cnt ++;
prime[cnt] = i;
mu[i] = -1;
}
for(int j = 1;i * prime[j] <= 10000000 && j <= cnt; j++) {
p[i * prime[j]] = 1;
if(i % prime[j] == 0)
break;
mu[i * prime[j]] = - mu[i];
}
}
for(int i = 1;i <= cnt; i++)
for(int j = 1;prime[i] * j <= 10000000; j++)
f[prime[i] * j] += mu[j];
for(int i = 1;i <= 10000000; i++)
sum[i] = sum[i - 1] + f[i];
}
long long H(int a,int b) {
long long res = 0;
if(a == 0)
return 0;
int l = 1,r = 0;
while(l <= n) {
r = min(a / (a / l),b / (b / l));
res += (sum[r] - sum[l - 1]) * (long long)(a / l) * (long long)(b / l);
l = r + 1;
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin >> T;
Seive();
while(T --> 0) {
cin >> n >> m;
if(n > m)
swap(n,m);
cout << H(n,m) << "\n";
}
return 0;
}
SDOI2014 数表#
题目大意#
有一张 的数表,其第 行第 列(,)的数值为能同时整除 和 的所有自然数之和。给定 ,计算数表中不大于 的数之和。
()。
思路#
形式化题意:
给定 ,求
为了方便处理,我们还是钦定 。
先不考虑 的限制,那么我们要求的就是
可以转化为
即
利用常规的变换技巧
换成整除分块形式
和上一道题类似,我们令 ,然后更改枚举顺序,有
这道题没有 的限制的部分就做完了,但还要考虑我们 的 。
我们设 ,我们发现,只有在 时,才会对 产生贡献。
那我们可以离线,把询问按照 从小到大排序,在从小到大逐个枚举询问的时候, 不断变大,使得一些 对 产生贡献。
我们枚举倍数来找到所有的 ,需要动态修改 且区间查询,考虑树状数组。
Code
#include <bits/stdc++.h>
using namespace std;
const int M = 1e5;
const int N = 100500;
const long long Mod = 1 << 31;
int T;
bool p[N];
int prime[N],cnt;
int num[N];
int sigma[N],mu[N];
unsigned long long ans[N];
struct Query{
int id;
int n,m,a;
bool operator < (const Query &t) const {
return a < t.a;
}
}q[N];
struct Num{
int x;
bool operator < (const Num &t) const {
return sigma[x] < sigma[t.x];
}
}a[N];
void Seive() {
sigma[1] = mu[1] = 1;
for(int i = 2;i <= M; i++) {
if(!p[i]) {
cnt ++;
prime[cnt] = i;
mu[i] = -1;
sigma[i] = num[i] = i + 1;
}
for(int j = 1;j <= cnt && i * prime[j] <= M; j++) {
p[i * prime[j]] = 1;
if(i % prime[j] == 0) {
mu[i * prime[j]] = 0;
num[i * prime[j]] = num[i] * prime[j] + 1;
sigma[i * prime[j]] = sigma[i] / num[i] * num[i * prime[j]];
break;
}
mu[i * prime[j]] = - mu[i];
num[i * prime[j]] = prime[j] + 1;
sigma[i * prime[j]] = sigma[i] * (prime[j] + 1);
}
}
for(int i = 1;i <= M; i++)
a[i].x = i;
sort(a + 1,a + M + 1);
return ;
}
int bit[N];
class BIT{
private:
int lowbit(int x) {
return x & (-x);
}
public:
void Add(int x,int y) {
for(int i = x;i <= M; i += lowbit(i))
bit[i] = (1ll * bit[i] + y) % Mod;
return ;
}
int Query(int x) {
int ans = 0;
for(int i = x;i >= 1; i -= lowbit(i))
ans = (1ll * ans + bit[i]) % Mod;
return ans;
}
}tree;
void Insert(int x) {
for(int k = 1;x * k <= M; k++)
tree.Add(x * k,1ll * mu[k] * sigma[x] % Mod);
// 预处理
}
int query(int n,int m) {
long long res = 0,last = 0,cur;
for(int l = 1,r = 0;l <= n;l = r + 1,last = cur) {
r = min(n / (n / l),m / (m / l));
cur = tree.Query(r);
res = (res + 1ll * (1ll * cur - last) * (n / l) % Mod * (m / l) % Mod) % Mod;
}
return res % Mod;
}
signed main() {
ios::sync_with_stdio(false);
cin >> T;
Seive();
for(int i = 1;i <= T; i++) {
cin >> q[i].n >> q[i].m >> q[i].a;
q[i].id = i;
if(q[i].n > q[i].m)
swap(q[i].n,q[i].m);
}
sort(q + 1,q + T + 1);
int j = 1;
for(int i = 1;i <= T; i++) {
for( ;j <= M && sigma[a[j].x] <= q[i].a; j++) {
Insert(a[j].x);
}
ans[q[i].id] = query(q[i].n,q[i].m);
}
for(int i = 1;i <= T; i++)
cout << (ans[i] % Mod + Mod) % Mod << "\n";
return 0;
}
BZOJ3309 DZY Loves Math#
题目大意#
对于正整数 ,定义 为 所包含质因子的最大幂指数。例如 ,,。
给定正整数 ,求下式的值:
推导#
首先记 。
设 (十分常用的技巧),那么有
记 ,那么有
那么,现在的问题是如何求 ?考虑求 。
我们可以把 进行质因数分解:
考虑 的原本写法:
记 ,。
考虑 的定义,当 中含有平方质因子时 ,即不会在 中产生贡献,可以不考虑。
所以 的质因数要满足最大幂指数小于 ,即 。
所以将 改写为如下形式时
有
记 。即 为 所包含质因子的最大幂指数, 为 所包含质因子幂指数中最大幂指数的个数。
我们发现 的取值只有 和 两种可能( 可能把最大幂指数的都取走,导致 的取值少了 )。
我们先按 和 两种情况分类讨论,在每一项讨论中,我们再分 和 两种子情况。
当 时,贡献为(加号前为 的情况,加号后为 的情况)
的情况不会把最大幂指数的质因数都取走。
所以我们枚举到 ,中间的 和 实质上就是莫比乌斯函数,最右边的二项式系数是枚举选取的方案。
将上面的式子加号右边的部分拆开,把带 的部分合并到左面,得到
二项式定理,得
当 时,
当 时,设在 个 的质数中选了 个,另外 个质数中选了 个,贡献为:
中间的 仍是莫比乌斯函数。因为 ,所以 个质数不能被选完,因此枚举到 。
左右拆开,分别二项式定理,得
当 时, 个 的质数一定全部被选择,设在另外 个质数中选择 个,贡献为:
所以最终 的表达式为
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e7;
const int M = 10500;
int T,n,m;
bool p[N + M];
int pri[N >> 2],cnt,low[N + M];
int h[N + M],a[N + M];
void Seive() {
p[1] = 1;
for(int i = 2;i <= N; i++) {
if(!p[i]) {
cnt ++;
low[i] = pri[cnt] = i;
a[i] = h[i] = 1;
}
for(int j = 1;j <= cnt && i * pri[j] <= N; j++) {
p[i * pri[j]] = 1;
if(i % pri[j] == 0) {
a[i * pri[j]] = a[i] + 1;
low[i * pri[j]] = low[i] * pri[j];
if(i == low[i])
h[i * pri[j]] = 1;
else if(a[i / low[i]] == a[i * pri[j]])
h[i * pri[j]] = -h[i / low[i]];
else
h[i * pri[j]] = 0;
break;
}
a[i * pri[j]] = 1;
low[i * pri[j]] = pri[j];
if(a[i] == 1)
h[i * pri[j]] = -h[i];
else
h[i * pri[j]] = 0;
}
}
for(int i = 1;i <= N; i++)
h[i] += h[i - 1];
return ;
}
signed main() {
ios::sync_with_stdio(false);
Seive();
cin >> T;
while(T --> 0) {
cin >> n >> m;
if(n > m)
swap(n,m);
int l = 1,r = 0,ans = 0;
while(l <= n) {
r = min(n / (n / l),m / (m / l));
ans += (n / l) * (m / l) * (h[r] - h[l - 1]);
l = r + 1;
}
cout << ans << "\n";
}
return 0;
}
SDOI2015 约数个数和#
题目大意#
设 为 的约数个数(即 ),给定 ,求
推导#
先给出一个 的性质
证明
设 中有因子 , 中有因子 , 的因子 中有因子 。显然 的每个因数对答案的贡献都是 ,我们考虑构建一种策略使得 的因数与数对 构成双射。
设数对为 。
- 如果 ,我们只在 中选择,即 ;
- 否则,我们在 中选择 ,在 中选择 (表示为 )。
当 时,我们把取满的部分赋为 ,即 。
显然, 中有且仅有一个数为 。
那么枚举 的约数,就转化为了枚举我们选择策略的数对 。
所以
即满足我们的选择策略。
这样就不重不漏地枚举了 的每一个约数,故等式成立。
那么利用上面的式子转化式子。
把 提前,
把 换成 ,
把 提前,
设函数 ,那么有
显然,。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int M = 50050;
int T,n,m;
int d[100500],num[100500],mu[100500],g[100500];
int pri[100500],cnt = 0;
bool p[100500];
void Seive() {
d[1] = 1;
mu[1] = 1;
for(int i = 2;i <= M; i++) {
if(!p[i]) {
cnt ++;
pri[cnt] = i;
d[i] = 2;
num[i] = 1;
mu[i] = -1;
}
for(int j = 1;j <= cnt && i * pri[j] <= M; j++) {
p[i * pri[j]] = 1;
if(i % pri[j] == 0) {
num[i * pri[j]] = num[i] + 1;
d[i * pri[j]] = d[i] / num[i * pri[j]] * (num[i * pri[j]] + 1);
break;
}
num[i * pri[j]] = 1;
d[i * pri[j]] = d[i] * 2;
mu[i * pri[j]] = -mu[i];
}
}
for(int i = 1;i <= M; i++) {
g[i] = g[i - 1] + d[i];
mu[i] += mu[i - 1];
}
return ;
}
signed main() {
ios::sync_with_stdio(false);
Seive();
cin >> T;
while(T --> 0) {
cin >> n >> m;
if(n > m)
swap(n,m);
int ans = 0;
for(int l = 1,r = 0;l <= n; l = r + 1) {
r = min(n / (n / l),m / (m / l));
ans += (mu[r] - mu[l - 1]) * g[n / l] * g[m / l];
}
cout << ans << "\n";
}
return 0;
}
SDOI2017 数字表格#
题目大意#
记 表示斐波那契数列的第 项,求
答案对 取模。
推导#
首先是经典枚举技巧,
指数部分单独取出来处理,直接跳步,得到
代回原式,
老套路,设 ,
把中间一部分提出来单独处理,
设
代入原式,就得到了
直接暴力求。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
int T,n,m;
const int N = 1005000;
const int M = 1000000;
const int Mod = 1e9 + 7;
bool p[N];
int pri[N],cnt;
int mu[N],inv[N];
int f[N],g[N];
long long Pow(long long a ,long long b) {
long long ans = 1 ;
long long base = a % Mod;
while( b > 0 ) {
if( b&1 != 0 )
ans = ans * base % Mod;
base = base * base % Mod;
b = b >> 1;
}
return ans;
}
void Seive() {
mu[1] = p[1] = f[1] = inv[1] = 1;
for(int i = 2;i <= M; i++) {
f[i] = (f[i - 1] + f[i - 2]) % Mod;
inv[i] = Pow(f[i],Mod - 2) % Mod;
if(!p[i]) {
mu[i] = -1;
cnt ++;
pri[cnt] = i;
}
for(int j = 1;j <= cnt && i * pri[j] <= M; j++) {
p[i * pri[j]] = 1;
if(i % pri[j] == 0) {
mu[i * pri[j]] = 0;
break;
}
mu[i * pri[j]] = -mu[i];
}
}
for(int i = 0;i <= M; i++)
g[i] = 1;
for(int i = 1;i <= M; i++) {
if(mu[i] == 0)
continue;
for(int j = i;j <= M; j += i) {
if(mu[i] == 1)
g[j] = g[j] * f[j / i] % Mod;
else
g[j] = g[j] * inv[j / i] % Mod;
}
}
for(int i = 2;i <= M; i++)
g[i] = g[i] * g[i - 1] % Mod;
return ;
}
signed main() {
ios::sync_with_stdio(false);
Seive();
cin >> T;
while(T --> 0) {
cin >> n >> m;
if(n > m)
swap(n,m);
int ans = 1,Inv = 0;
for(int l = 1,r = 0;l <= n; l = r + 1) {
// cerr << "a";
r = min(n / (n / l),m / (m / l));
Inv = g[r] * Pow(g[l - 1],Mod - 2) % Mod;
ans = ans * Pow(Inv,(n / l) * (m / l) % (Mod - 1)) % Mod;
}
ans = (ans + Mod) % Mod;
cout << ans << "\n";
}
return 0;
}
BZOJ4407 于神之怒加强版#
题目大意#
给定 ,计算
对 取模的结果。
推导#
经典枚举
令 ,即 ,显然这是一个积性函数。
当 为质数时,,容易得到 。
考虑 为质数时, 的值:
当 时,有 ,不产生贡献。产生贡献的只有 和 两种情况。
容易得到 。
考虑 , 的值,根据积性函数的性质即可。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
int T,k,n,m;
const int N = 5005000;
const int M = 5000000;
const int Mod = 1e9 + 7;
bool p[N];
int pri[N],cnt;
int g[N],sum[N];
long long Pow(long long a ,long long b) {
long long ans = 1 ;
long long base = a % Mod;
b = b % (Mod - 1);
while(b) {
if(b & 1)
ans = ans * base % Mod;
base = base * base % Mod;
b >>= 1;
}
return ans;
}
void Seive() {
memset(g,1,sizeof(g));
g[1] = 1;
for(int i = 2;i <= M; i++) {
if(!p[i]) {
cnt ++;
pri[cnt] = i;
g[i] = (Pow(i,k) - 1 + Mod) % Mod;
}
for(int j = 1;j <= cnt && i * pri[j] <= M; j++) {
p[i * pri[j]] = 1;
if(i % pri[j] == 0) {
g[i * pri[j]] = g[i] * (g[pri[j]] + 1) % Mod;
break;
}
g[i * pri[j]] = g[i] * g[pri[j]] % Mod;
}
}
for(int i = 1;i <= M; i++)
sum[i] = (sum[i - 1] + g[i]) % Mod;
return ;
}
signed main() {
ios::sync_with_stdio(false);
cin >> T >> k;
Seive();
while(T --> 0) {
cin >> n >> m;
if(n > m)
swap(n,m);
int ans = 0;
for(int l = 1,r = 0;l <= n; l = r + 1) {
r = min(n / (n / l),m / (m / l));
ans = (ans + (sum[r] - sum[l - 1]) % Mod * (n / l) % Mod * (m / l)) % Mod;
ans = (ans + Mod) % Mod;
}
ans = (ans + Mod) % Mod;
cout << ans << "\n";
}
return 0;
}
Crash 的数字表格 / JZPTAB#
题目大意#
给定 ,求
答案对 取模。
推导#
经典枚举
提出 , 都除以了 ,多除的一个放到前面去,
然后得
设 ,
设 ,有
设 ,显然是积性函数。
当 为质数时,有 。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 10005000;
const int M = 10000000;
const int Mod = 20101009;
int n,m;
bool p[N];
int cnt,pri[N >> 4];
int f[N];
void Seive() {
f[1] = 1;
for(int i = 2;i <= M; i++) {
if(!p[i]) {
cnt ++;
pri[cnt] = i;
f[i] = (1 - i) % Mod;
}
for(int j = 1;j <= cnt && i * pri[j] <= M; j++) {
p[i * pri[j]] = 1;
if(i % pri[j] == 0) {
f[i * pri[j]] = f[i];
break;
}
f[i * pri[j]] = f[i] * f[pri[j]] % Mod;
}
}
for(int i = 2;i <= M; i++)
f[i] = (f[i] * i % Mod + f[i - 1]) % Mod;
return ;
}
int g(int x) {
return (x * (x + 1) / 2) % Mod;
}
signed main() {
ios::sync_with_stdio(false);
Seive();
cin >> n >> m;
if(n > m)
swap(n,m);
int ans = 0;
for(int l = 1,r = 0;l <= n; l = r + 1) {
r = min(n / (n / l),m / (m / l));
ans = (ans + g(n / l) * g(m / l) % Mod * (f[r] - f[l - 1] + Mod) % Mod + Mod) % Mod;
}
cout << ans << "\n";
return 0;
}
作者:白简
出处:https://www.cnblogs.com/baijian0212/p/Mobius-Reverse.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异