【9】莫比乌斯反演学习笔记
好像咕了很久。><
\(\text{Upd on 2026.7.14}\):优化了一些讲解,改正了一些笔误,添加了一些题目。
前置知识
莫比乌斯函数:
定义:
其中 \(p_1,p_2,\dots,p_w\) 两两不同。
莫比乌斯函数是 积性函数,可以使用 线性筛。
狄利克雷卷积
对于两个数论函数 \(f(x)\) 和 \(g(x)\),他们的 狄利克雷 卷积得到的结果 \(h(x)\) 为:
可以简记成 \(f * g = h\)。
常用狄利克雷卷积:
-
记 \(I(n)=n\),\(1(n)=1\),\(\varphi(n)=\displaystyle\sum_{d=1}^{n}[\gcd(n,d)=1]\),则有 \(\varphi * 1 = I\)。
写成 \(\Sigma\) 形式即为,\(\displaystyle\sum_{d|n}\varphi(d)=n\)。
这个和文章主题没啥关系,证明留作读者习题。(逃
-
记 \(\varepsilon(n)=[n=1]\),则 \(\mu * 1 = \varepsilon\)。重点,后面大多依赖这个性质推式子!
写成 \(\Sigma\) 形式即为 \(\displaystyle\sum_{d|n}\mu(d)=[n=1]\)。
如何证明?可以这样理解:
当 \(n>1\) 时,设 \(n=\displaystyle\prod_{i=1}^{k}p_i^{\alpha_i}\),那么对于每一个 \(d\),其在每一个 \(p_i\) 上的幂次只有取 \(0/1\) 才能有值,否则为 \(0\);那么我们就只考虑这些 \(\mu\) 值非 \(0\) 的 \(d\)。根据定义,我们可以把所有这样的 \(d\) 都看成一个二进制串,第 \(i\) 位对应 \(p_i\) 上的幂次。如果这个二进制串有奇数个 \(1\),那么 \(\mu(d)=1\);否则 \(\mu(d)=-1\)。
如何证明奇数个 \(1\) 的串和偶数个 \(1\) 的串个数相同呢?考虑固定前面 \(k-1\) 位,那么最后一位取 \(0/1\) 时一定会使这个串中 \(1\) 的个数奇偶性不同。那么由此可得两种串个数相等,那么最终加起来也就是 \(0\) 了。
而当 \(n=1\) 时,\(d\) 只能取 \(1\),此时 \(\mu(d)=1\),所以总和也就是 \(1\) 了。
整除分块
例子:给定 \(n\),求 \(\displaystyle\sum_{i=1}^{n}\Big\lfloor\dfrac{n}{i}\Big\rfloor\) 的值。\(n \le 10^{12}\)
首先暴力做是 \(O(n)\) 的。
然后不难注意到对于某些连续的 \(i\),\(\Big\lfloor\dfrac{n}{i}\Big\rfloor\) 都是一样的。
可以发现对于一个数 \(L\) 使方程 \(\Big\lfloor\dfrac{n}{L}\Big\rfloor=\Big\lfloor\dfrac{n}{R}\Big\rfloor\) 成立的最大 \(R\) 是 \(\Big\lfloor\dfrac{n}{\lfloor\frac{n}{L}\rfloor}\Big\rfloor\),那么可以写出如下代码:
for(int l = 1, r = 1;l <= n;l = r + 1) {
r = n / (n / l);
ans += (r - l + 1) * (n / l);
}
时间复杂度 \(O(\sqrt n)\)。
莫比乌斯反演
若 \(f(x)=\displaystyle\sum_{d|x}F(d)\),则 \(F(x)=\displaystyle\sum_{d|x}\mu(d)f(\frac xd)\)。这个式子被称作 莫比乌斯反演。
(没错讲完了)
下面用一些例题来说明莫比乌斯反演的过程。
在这里先写一点常用变形,待会儿如果推式子哪里看不懂可以切回来看:
-
提因子
考虑这个式子:\(\displaystyle\sum_{i=1}^{n}\sum_{d|i}\mu(d)\)
如何变形?可以把 \(d\) 提到前面来作为第一层枚举,则后面枚举时保证 \(i\) 为 \(d\) 的倍数即可,可以写成
\[\displaystyle\sum_{d=1}^{n}\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}1=\sum_{d=1}^n\mu(d)\lfloor\frac{n}{d}\rfloor \]这样就变形好了。
题
P3455 [POI 2007] ZAP-Queries
下面记 \(T=n,n=a,m=b\)。
不妨 \(n<m\)。
线性筛出莫比乌斯函数,即可做到 \(O(V+T\sqrt n)\),可以通过。
代码:(高亮处为整除分块)
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5;
int mu[N]; bool isp[N];
vector<int> pr;
int solve(int n, int m) {
int ans = 0;
for(int i = 1, j = 1;i <= min(n, m);i = j + 1) {
j = min(n / (n / i), m / (m / i)); ans += (mu[j] - mu[i-1]) * (n / i) * (m / i);
} return ans;
} int main() {
mu[1] = 1;
for(int i = 2;i <= 50000;i++) {
if(!isp[i]) pr.push_back(i), mu[i] = -1;
for(auto j : pr) {
if((i * j) > 50000) break;
isp[i * j] = true;
if(i % j == 0) {
mu[i * j] = 0; break;
} else mu[i * j] = -mu[i];
}
} for(int i = 2;i <= 50000;i++) mu[i] += mu[i-1];
int n; cin >> n; while(n--) {
int a, b, k; cin >> a >> b >> k;
cout << solve(a / k, b / k) << endl;
}
return 0;
}
P1829 [国家集训队] Crash的数字表格 / JZPTAB
不妨 \(n < m\)。
预处理 \(\mu(i)i^2\) 的前缀和(注:积性函数的乘积还是积性函数,所以还是可以用线性筛),可以做到 \(O(n)\)。可以通过。
注意这里需要处理一下 \(2\) 的逆元。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e7 + 5, mod = 20101009, m2 = 10050505;
int mu[N], s[N]; bool isp[N];
vector<int> pr;
int g(int n, int m) { return 1ll * n * (n + 1) % mod * m2 % mod * 1ll * m % mod * (m + 1) % mod * 10050505 % mod; }
int sum(int n, int m) {
int ans = 0;
for(int l = 1, r = 1;l <= min(n, m);l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += (s[r] - s[l-1] + mod) * g(n / l, m / l) % mod;
} return ans;
} int solve(int n, int m) {
int ans = 0;
for(int l = 1, r = 1;l <= min(n, m);l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans = (ans + 1ll * (l + r) * (r - l + 1) / 2 % mod * sum(n / l, m / l) % mod) % mod;
} return ans;
} signed main() {
mu[1] = 1;
for(int i = 2;i <= 10000000;i++) {
if(!isp[i]) pr.push_back(i), mu[i] = -1;
for(auto j : pr) {
if((i * j) > 10000000) break;
isp[i * j] = true;
if(i % j == 0) {
mu[i * j] = 0; break;
} else mu[i * j] = -mu[i];
}
} for(int i = 1;i <= 10000000;i++)
s[i] = (s[i-1] + 1ull * i * i % mod * (mu[i] + mod) % mod) % mod;
int n, m; cin >> n >> m;
cout << solve(n, m) << endl;
return 0;
}
P3327 [SDOI2015] 约数个数和
首先有一个神奇式子:
直接带入原题:
依然不妨 \(n<m\)。
套两层整除分块,即可做到 \(O(n)\)。
如果预处理内层整除分块,可以做到 \(O(n\sqrt n+T\sqrt n)\),可以通过。
高亮部分是预处理。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 6e4 + 5;
bool isp[N]; int mu[N], s[N]; vector<int> pr;
int h(int n) { int ans = 0;
for(int l = 1, r = 1;l <= n;l = r + 1)
r = n / (n / l), ans += (r-l+1) * (n/l);
return ans;
} int solve(int n, int m) { int ans = 0;
for(int l = 1, r = 1;l <= min(n, m);l = r + 1)
r = min(n / (n / l), m / (m / l)),
ans += (mu[r] - mu[l-1]) * s[n / l] * s[m / l];
return ans;
} signed main() { mu[1] = 1;
for(int i = 2;i <= 60000;i++) {
if(!isp[i]) pr.push_back(i), mu[i] = -1;
for(auto j : pr) {
if(i * j > 60000) break;
isp[i * j] = true;
if(i % j == 0) {
mu[i * j] = 0; break;
} mu[i * j] = -mu[i];
}
} for(int i = 2;i <= 60000;i++) mu[i] += mu[i-1];
for(int i = 1;i <= 60000;i++) s[i] = h(i);
int t; cin >> t; while(t--) {
int n, m; cin >> n >> m; cout << solve(n, m) << endl;
} return 0;
}
P6156 简单题
真 · 简单题
首先,你要发现这个题里有 \(f(x)=\mu^2(x)\)。然后就有:
\(d^{k+1}\mu^2(d)\) 和 \(\mu(i)i^k\) 可以线性筛 \(O(n)\) 求出(两个积性函数的积还是积性函数),接下来考虑后面那个式子。
记
考虑如何从 \(f(x-1)\) 递推到 \(f(x)\)。做一下差可以发现:
哦哈哈那不是又回到预处理 \(k\) 次方了那线性筛就完了。
关于线性筛 \(k\) 次幂:质数用快速幂,剩下的拿积性函数性质乘。复杂度不会算,但是接近于 \(O(n)\)。
时间复杂度 \(O(n+\sqrt n)\),可以通过。
注意根据欧拉定理 \(k\) 和 \(k \bmod \varphi(p)\) 时的答案相同,所以我们先模一下 \(\varphi(p)=p-1\)。
注意精细取模。注意莫比乌斯函数会是负数。啊啊啊啊啊!!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int N = 1e7 + 5, _N = 1e7; bool isp[N];
int mu[N], K[N], sk[N], muk[N], mu2k[N];
long long f[N];
vector<int> pr;
ll qpow(ll a, ll b) {
ll c = 1;
while(b) { if(b & 1) c = (c * a) % mod; a = (a * a) % mod; b >>= 1; }
return c;
} ll g(ll x) { ll _ans = 0;
for(int l = 1, r = 1;l <= x;l = r + 1)
r = x / (x / l),
_ans = (_ans + 1ll * (muk[r] - muk[l-1] + 3ll * mod) % mod * f[x / l] % mod) % mod;
return _ans;
} signed main() {
mu[1] = 1; K[1] = 1;
int n; ll k; cin >> n >> k; k %= (mod - 1);
for(int i = 2;i <= _N;i++) {
if(!isp[i]) {
pr.push_back(i); K[i] = qpow(i, k);
mu[i] = -1;
} for(auto j : pr) {
if(i * j > _N) break;
isp[i * j] = 1;
if(i % j == 0) {
mu[i * j] = 0; K[i * j] = (1ll * K[i] * K[j]) % mod; break;
} mu[i * j] = -mu[i]; K[i * j] = (1ll * K[i] * K[j]) % mod;
}
} for(int i = 1;i <= _N;i++) {
muk[i] = (muk[i-1] + 1ll * mu[i] * K[i] % mod) % mod;
mu2k[i] = (mu2k[i-1] + 1ll * mu[i] * mu[i] * K[i] % mod * i % mod) % mod;
sk[i] = (sk[i-1] + K[i]) % mod;
} for(int i = 1;i * 2 <= _N;i++)
f[i] = (f[i-1] + sk[2 * i - 1]) % mod,
f[i] = (f[i] - 2ll * sk[i] % mod + 2ll * mod) % mod,
f[i] = (f[i] + sk[2 * i]) % mod;
ll ans = 0;
for(int l = 1, r = 1;l <= n;l = r + 1)
r = n / (n / l),
ans = (ans + 1ll * (mu2k[r] - mu2k[l-1] + mod) % mod * g(n / l) % mod) % mod;
cout << ans << endl;
return 0;
}
P4449 于神之怒加强版
仍然不妨 \(n<m\),前面几步是套路的:
但是做到这里好像就没啥头绪了。那咋办呢?
考虑换元。设 \(T=dw\),然后我们换成先枚举 \(T\),也就是先枚举 \(dw\)。然后我们再枚举 \(d\),即可得到 \(w\)。此时式子变形如下:
前面的很好处理,后面的那个 \(\sigma\) 咋办呢?我们先拉出来,记 \(g(T)=\sum_{d|T}d^k\mu(\frac Td)\)。我们考虑怎么快速处理这个。
事实上,由于 \(x^k\) 和 \(\mu(x)\) 都是积性函数,所以 \(g\) 也是积性函数。那么我们考虑用这个性质线性筛出 \(g\)。
所以我们只需要会算 \(g(p^\alpha)\) 即可,其中 \(p\) 为质数。
那么这个是啥呢?也就是 \(g(p^\alpha)=\sum_{i=0}^{\alpha}p^{ik}\mu(p^{\alpha-i})\)。
由 \(\mu\) 的定义可以得到,当 \(\alpha-i \ge 2\) 的时候,里面的式子应当都是 \(0\)。
所以式子化简为:\(g(p^\alpha)=p^{\alpha k}-p^{(\alpha-1)k}\)。
那么可以得到递推式:\(g(p)=p^k-1,g(p^\alpha)=g(p^{\alpha-1})\times p^k\)。
这样就可以用 \(O(n)\) 的时间求得所有 \(g\) 了。然后做一个前缀和,对于剩下的部分跑整除分块即可。
时间复杂度 \(O(V+T\sqrt n)\)。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e6 + 5, mod = 1e9 + 7;
bool isp[N]; vector<pair<int, int>> pr; int g[N];
int qpow(int a, int b) {
int c = 1;
while(b) {
if(b & 1) c = (c * a) % mod;
a = (a * a) % mod; b >>= 1;
} return c;
} signed main() {
int T, k; cin >> T >> k; g[1] = 1;
for(int i = 2;i <= N - 5;i++) {
if(!isp[i])
pr.push_back({i, qpow(i, k)}),
g[i] = (qpow(i, k) - 1 + mod) % mod;
for(auto [x, y] : pr) {
if((i * x) > (N - 5)) break;
isp[i * x] = 1;
if(i % x) g[i * x] = (g[i] * (y - 1 + mod)) % mod;
else { g[i * x] = (g[i] * y) % mod; break; }
}
} for(int i = 1;i <= N - 5;i++)
g[i] = (g[i] + g[i-1]) % mod;
while(T--) {
int n, m; cin >> n >> m; if(n > m) swap(n, m);
int sum = 0;
for(int L = 1, R;L <= n;L = R + 1)
R = min(n / (n / L), m / (m / L)),
sum = (sum + ((((g[R] - g[L-1] + mod) % mod) * (n / L) % mod) * (m / L) % mod)) % mod;
cout << sum << endl;
}
return 0;
}
后记
码字不易,能否给个赞 /wq ><
若对文章有任何问题和建议可以与作者私信交流。

浙公网安备 33010602011771号