学习笔记:莫比乌斯函数
前置知识:迪利克雷卷积,整除分块。
莫比乌斯函数
\(\mu(n) = \begin{cases} 1 & \text{if } n=1, \\ 0 & \text{if } n \text{ 有平方因子}, \\ (-1)^k & k=n \text{ 的质因子个数}. \end{cases}\)
积性函数,即 \(\gcd(a,b) = 1\) 时,\(\mu(a)\mu(b) = \mu(ab)\)。
基本性质
性质 1
莫比乌斯函数是数论函数 \(1(n) = 1\) 的迪利克雷逆,即 \(\mu * 1 = \epsilon\)。
证明在性质 2。
性质 2
\(\sum_{d\mid n}\mu(d) = \epsilon(n)\),
证明
设 \(n = p_1^{c_1}p_2^{c_2}\text{ ... }p_k^{c_k}\),\(n' = p_1p_2\text{ ... }p_k\),\(p\) 都是质数,就是唯一分解定理,
则 \(\sum_{d\mid n}\mu(d) = \sum_{d\mid n'}\mu(d)\),因为只有次数为 \(1\) 的质数才有贡献,不然就有平方因子,贡献为 \(0\),
则 \(\sum_{d\mid n'}\mu(d) = \sum_{i=0}^{k}(_{i}^{k})(-1)^i\),
然后根据二项式定理,\(\sum_{i=0}^{k}(_{i}^{k})(-1)^i = (1 + (-1))^k\),
因此当且仅当 \(k=0\) 即 \(n=1\) 时,\(\sum_{d\mid n}\mu(d) = 1\),否则为 \(0\)。
同时也证明了性质 1。
该性质有一些比较常见的运用,例如,
- \([\gcd(i, j) = 1] = \sum_{d\mid \gcd(i, j)}\mu(d)\)
性质 3 ( 莫比乌斯变换 / 逆变换(反演) )
一般有两个形式。
形式 1
若 \(f(n) = \sum_{d\mid n} g(d)\),则 \(g(n) = \sum_{d\mid n}\mu(d)f(n/d)\)。
我们称 \(f(n)\) 是 \(g(n)\) 的莫比乌斯变换,\(g(n)\) 是 \(f(n)\) 的莫比乌斯反演。
证明
\(\sum_{d\mid n}\mu(d)f(n/d) = \sum_{d\mid n}\mu(d)\sum_{k\mid \frac{n}{d}}g(k) = \sum_{k\mid n}g(k)\sum_{d\mid \frac{n}{k}}\mu(d) = g(n)\)。
或者用迪利克雷卷积证明,
\(f(n) = \sum_{d\mid n} g(n) \to f = g * 1 \to g = f * \mu \to g(n) = \sum_{d\mid n}\mu(d)f(n/d)\)。
形式 2
若 \(f(n) = \sum_{n\mid d}g(d)\),则 \(g(n) = \sum_{n\mid d}\mu(d/n)f(d)\)。
证明
\(\sum_{n\mid d}\mu(d/n)f(d) = \sum_{k=1}^{\inf}\mu(k)f(kn) = \sum_{k=1}^{inf}\mu(k)\sum_{kn\mid d}g(d) = \sum_{n\mid d}g(d)\sum_{k\mid \frac{d}{n}}\mu(k) = g(n)\)。
练习
P3455 [POI2007] ZAP-Queries
Solution
设 \(n<m\),由题意,
\(\sum_{i=1}^{n}\sum_{j=1}^{m}[\gcd(i, j)=d]\)
\(=\sum_{i=1}^{\lfloor n/d\rfloor}\sum_{j=1}^{\lfloor m/d\rfloor}[\gcd(i, j)=1]\)
\(=\sum_{i=1}^{\lfloor n/d\rfloor}\sum_{j=1}^{\lfloor m/d\rfloor}\sum_{k\mid\gcd(i, j)}\mu(k)\)
\(=\sum_{k=1}^{n}\mu(k)(\lfloor n/kd\rfloor)(\lfloor m/kd\rfloor)\)
根据整除分块以及整除点的性质可知,使得 \((\lfloor n/kd\rfloor)(\lfloor m/kd\rfloor)\) 不同的 \(k\) 的个数只有 \(O(\sqrt n)\) 个。于是先 \(O(n)\) 预处理 \(\mu\) 的前缀和,然后遍历 \(n\) 和 \(m\) 的整除点,计算贡献即可。
这段也可以用莫比乌斯反演来推。
有一个常见的技巧,互质不方便直接判断,但是判断 \(i\) 和 \(j\) 的 \(\gcd\) 是不是某个数 \(k\) 的倍数是简单的,只需要 \(k\) 同时是 \(i\) 和 \(j\) 的约数。
设 \(f(d) = \sum_{i=1}^{n}\sum_{j=1}^{m}[\gcd(i, j) = d]\),
令 \(g(k) = \sum_{k\mid d} f(d)\),
容易得到 \(g(k) = \lfloor \frac{n}{k} \rfloor \lfloor \frac{m}{k} \rfloor\),
根据莫比乌斯反演形式 2,\(f(k) = \sum_{k\mid d} \mu(\frac{d}{k}) g(d)\)。
枚举 \(x = \frac{d}{k}\),于是 \(f(k) = \sum_{x=1}^{n} \mu(x) g(xk) = \sum_{x=1}^{n} \mu(x) \lfloor \frac{n}{xk} \rfloor \lfloor \frac{m}{xk} \rfloor\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 5e4 + 5;
int n, m, d;
int mob[N], prime[N], vis[N], cnt;
ll sum[N];
void Solve() {
cin >> n >> m >> d;
n /= d, m /= d;
ll ans = 0, l1 = 1, r1 = 1, l2 = 1, r2 = 1;
while (l1 <= n && l2 <= m) {
r1 = n / (n / l1);
r2 = m / (m / l2);
ans += (sum[min(r1, r2)] - sum[max(l1, l2) - 1]) * (n / r1) * (m / r2);
// cout << l1 << ' ' << r1 << ' ' << l2 << ' ' << r2 << '\n';
if (r1 <= r2) l1 = r1 + 1;
if (r1 >= r2) l2 = r2 + 1;
}
cout << ans << '\n';
}
int main() {
cnt = 0;
mob[1] = sum[1] = 1;
for (int i = 2; i <= 5e4; i++) {
if (!vis[i]) {
prime[++cnt] = i;
vis[i] = i;
mob[i] = -1;
}
sum[i] = sum[i - 1] + mob[i];
for (int j = 1; j <= cnt && prime[j] <= 5e4 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[i * prime[j]] = 0;
break;
} else {
mob[i * prime[j]] = -mob[i];
}
}
}
int T;
cin >> T;
while (T--) Solve();
return 0;
}
P2257 YY的GCD
Solution
设 \(n < m\),由题意,
\(\sum_{i=1}^{n} \sum_{j=1}^{m} [\gcd(i,j)\in prime]\)
\(= \sum_{k\in prime} \sum_{i=1}^{n} \sum_{j=1}^{m} [\gcd(i,j)=k]\)
\(= \sum_{k\in prime} \sum_{i=1}^{\lfloor n/k\rfloor} \sum_{j=1}^{\lfloor m/k\rfloor} [\gcd(i,j)=1]\)
\(= \sum_{k\in prime} \sum_{i=1}^{\lfloor n/k\rfloor} \sum_{j=1}^{\lfloor m/k\rfloor} \sum_{d\mid\gcd(i,j)}\mu(d)\)
\(= \sum_{k\in prime} \sum_{d=1}^n\mu(d) \lfloor n/kd\rfloor \lfloor m/kd\rfloor\)
设 \(x = kd\),则,
原式 \(= \sum_{k\in prime} \sum_{d=1}^n\mu(d) \lfloor n/x\rfloor \lfloor m/x\rfloor\)
考虑枚举 \(x\),则,
原式 \(= \sum_{x=1}^n \lfloor n/x\rfloor \lfloor m/x\rfloor \sum_{k\mid x,k\in prime}\mu(x/k)\)
前一半 \(O(\sqrt n)\) 整除分块,后一半可以近似 \(O(n)\) 预处理。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 1e7 + 5;
int n, m;
int prime[N], cnt, vis[N], mob[N];
ll res[N], sum[N];
void Solve() {
cin >> n >> m;
int l1 = 1, r1, l2 = 1, r2;
ll ans = 0;
while (l1 <= n && l2 <= m) {
r1 = n / (n / l1);
r2 = m / (m / l2);
ans += (1ll * n / r1) * (1ll * m / r2) * (sum[min(r1, r2)] - sum[max(l1, l2) - 1]);
if (r1 <= r2) l1 = r1 + 1;
if (r2 <= r1) l2 = r2 + 1;
}
cout << ans << '\n';
}
int main() {
mob[1] = 1;
for (int i = 2; i <= 1e7; i++) {
if (!vis[i]) {
prime[++cnt] = i;
vis[i] = i;
mob[i] = -1;
}
for (int j = 1; j <= cnt && prime[j] <= 1e7 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[prime[j] * i] = 0;
break;
} else {
mob[prime[j] * i] = -mob[i];
}
}
}
for (int i = 1; i <= cnt; i++) {
for (int j = prime[i]; j <= 1e7; j += prime[i]) {
res[j] += (ll)mob[j / prime[i]];
}
}
for (int i = 1; i <= 1e7; i++) {
sum[i] = sum[i - 1] + res[i];
}
int T;
cin >> T;
while (T--) Solve();
return 0;
}
P3312 [SDOI2014] 数表
Solution
设 \(n < m\),由题意,
\(\sum_{i=1}^{n}\sum_{j=1}^{m}\sigma_1(\gcd(i, j))\),
其中 \(\sigma_1(n)\) 是约数和函数。
原式 \(= \sum_{d=1}^{n}\sum_{i=1}^{n}\sum_{j=1}^{m}\sigma_1(d)\times [\gcd(i, j) = d]\)
\(= \sum_{d=1}^{n}\sigma_1(d)\sum_{i=1}^{n/d}\sum_{j=1}^{m/d}[\gcd(i, j) = 1]\)
\(= \sum_{d=1}^{n}\sigma_1(d)\sum_{i=1}^{n/d}\sum_{j=1}^{m/d}\sum_{k\mid \gcd(i,j)}\mu(k)\)
\(= \sum_{d=1}^{n}\sigma_1(d)\sum_{k=1}^{n}\mu(k)\lfloor\frac{n}{kd}\rfloor\lfloor\frac{m}{kd}\rfloor\)
设 \(x=kd\),则,
原式 \(= \sum_{d=1}^{n}\sigma_1(d)\sum_{k=1}^{n}\mu(k)\lfloor\frac{n}{x}\rfloor\lfloor\frac{m}{x}\rfloor\)
\(= \sum_{x=1}^{n}\lfloor\frac{n}{x}\rfloor\lfloor\frac{m}{x}\rfloor\sum_{d\mid x}\sigma_0(d)\mu(\frac{x}{d})\)
如果没有限制 \(a\),那么这样就已经做完了,整除分块加上预处理。如果加上限制 \(a\) 的话,那么最终式子中的 \(\sigma_0(d)\) 只有 \(\le a\) 才会产生贡献。
那么我们考虑离线处理,按照询问的 \(a\) 升序排序,然后把预处理出来的 \(\sigma_0(d)\) 也按照升序排序,对于每个询问,我们都把 \(\le a\) 的 \(\sigma_0(d)\) 枚举它的倍数预处理。
因此需要实时更改前缀和,于是树状数组维护即可。时间复杂度 \(O(q\sqrt n\log n + n\ln n\log n) = O(\text{能过})\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 1e5 + 5;
const ll mod = 1ll << 31;
int q;
ll ans[N];
ll prime[N], vis[N], mob[N], c[N], p[N]; int cnt;
// c[i] 是 i 的最小质因子的次数。
// p[i] 是 i 的最小质因子的 c[i] 次方。
struct Info {
int n, m, idx;
ll a;
bool operator < (const Info &_) const { return a < _.a; }
} A[N];
struct Sigma { // σ1函数
ll val, x;
bool operator < (const Sigma &_) const { return val < _.val; }
} sig[N];
struct BitTree {
int n;
vector<ll> a;
void init(int _n) {
n = _n;
a.assign(n + 5, 0);
}
void add(int x, ll v) {
for (; x <= n; x += (x & -x)) a[x] += v;
}
ll sum(int x) {
ll ans = 0;
for (; x; x -= (x & -x)) ans += a[x];
return ans;
}
ll sum(int l, int r) {
return sum(r) - sum(l - 1);
}
} t;
ll work(int n, int m) {
int l = 1, r;
ll ans = 0;
if (n > m) swap(n, m);
for (; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += t.sum(l, r) * (1ll * n / l) * (1ll * m / l);
}
return ans;
}
int main() {
mob[1] = 1;
sig[1] = {1, 1};
for (int i = 2; i <= 1e5; i++) {
if (!vis[i]) {
vis[i] = i;
prime[++cnt] = i;
mob[i] = -1;
sig[i] = {i + 1, i};
c[i] = 1;
p[i] = i;
}
for (int j = 1; j <= cnt && prime[j] <= 1e5 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[prime[j] * i] = 0;
c[prime[j] * i] = c[i] + 1;
p[prime[j] * i] = p[i] * prime[j];
if (p[i] == i) {
sig[prime[j] * i] = {sig[i].val + p[i] * prime[j], prime[j] * i};
} else {
sig[prime[j] * i] = {sig[i / p[i]].val * sig[p[i] * prime[j]].val, prime[j] * i};
}
break;
} else {
c[prime[j] * i] = 1;
p[prime[j] * i] = prime[j];
mob[prime[j] * i] = -mob[i];
sig[prime[j] * i] = {sig[i].val * sig[prime[j]].val, prime[j] * i};
}
}
}
sort(sig + 1, sig + 1 + (int)1e5);
t.init(1e5);
cin >> q;
for (int i = 1; i <= q; i++) {
cin >> A[i].n >> A[i].m >> A[i].a;
A[i].idx = i;
}
sort(A + 1, A + 1 + q);
for (int i = 1, j = 1; i <= q; i++) {
while (j <= 1e5 && sig[j].val <= A[i].a) {
for (int k = sig[j].x; k <= 1e5; k += sig[j].x) {
t.add(k, sig[j].val * mob[k / sig[j].x]);
}
j++;
}
ans[A[i].idx] = work(A[i].n, A[i].m);
}
for (int i = 1; i <= q; i++) cout << ans[i] % mod << '\n';
return 0;
}
P3327 [SDOI2015] 约数个数和
Solution
重要性质:\(\sigma_0(ij) = \sum_{x \mid i} \sum_{y \mid j} [\gcd(x, y) = 1]\),
证明:
设 \(k \mid ij\),\(k = p_1^{c_1}p_2^{c_2}...p_t^{c_t}\),
那么对于 \(k\) 的每个质因子 \(p_i\),我们要保证 \(i\) 中 \(p\) 的次数为 \(a\),\(j\) 中 \(p\) 的次数为 \(b\),且 \(a + b \ge c_i\),
通俗来说,相当于可以从 \(i\) 中拿了 \(a\) 个 \(p\) 出来,\(j\) 中拿了 \(b\) 个 \(p\) 出来,满足 \(a + b = c_i\),
那么我们钦定 \(i\) 中如果 \(a = p\),也就是直接能拿够就直接拿,否则在 \(j\) 中拿剩下的 \(b = p - a\) 个,
因此不会同时从 \(i\) 和 \(j\) 拿出相同的质因子,所以 \(\gcd(x, y) = 1\),代表从 \(i\) 中拿出了 \(x\),从 \(j\) 中拿出了剩余不够的 \(y\),凑成了一个 \(ij\) 的因子 \(k\)。
\(\sum_{i = 1}^{n} \sum_{j = 1}^{m} \sigma_0(ij)\)
\(= \sum_{i = 1}^{n} \sum_{j = 1}^{m} \sum_{x \mid i} \sum_{y \mid j} [\gcd(x, y) = 1]\)
\(= \sum_{x = 1}^{n} \sum_{y = 1}^{m} \lfloor{\frac{n}{x}}\rfloor \lfloor{\frac{m}{y}}\rfloor [\gcd(x, y) = 1]\)
设 \(f(d) = \sum_{x = 1}^{n} \sum_{y = 1}^{m} \lfloor{\frac{n}{x}}\rfloor \lfloor{\frac{m}{y}}\rfloor [\gcd(x, y) = d]\),
令 \(g(d) = \sum_{d \mid k} f(k)\),
易知,\(g(d) = \sum_{x = 1}^{n} \sum_{y = 1}^{m} \lfloor{\frac{n}{x}}\rfloor \lfloor{\frac{m}{y}}\rfloor [d \mid \gcd(x, y)]\),
将限制条件同时除去 \(d\),得,\(g(d) = \sum_{x = 1}^{\frac{n}{d}} \sum_{y = 1}^{\frac{m}{d}} \lfloor{\frac{n}{dx}}\rfloor \lfloor{\frac{m}{dy}}\rfloor\),
由莫比乌斯反演,得,\(f(d) = \sum_{d \mid k} \mu(\frac{k}{d}) g(k)\),
设 \(h(x) = \sum_{i = 1}^{x} \lfloor{\frac{x}{i}}\rfloor\),\(O(n \sqrt{n})\) 即可预处理,
易知,\(g(d) = h(\lfloor{\frac{n}{d}}\rfloor) h(\lfloor{\frac{m}{d}}\rfloor)\),于是便能快速算出 \(g\) 函数,
由题意,最终答案即 \(f(1)\),
\(f(1) = \sum_{1 \mid k} \mu(k) g(k) = \sum_{k = 1}^{n} \mu(k) g(k) = \sum_{k = 1}^{n} \mu(k) h(\lfloor{\frac{n}{k}}\rfloor) h(\lfloor{\frac{m}{k}}\rfloor)\),
于是整除分块即可。
时间复杂度 \(O(n \sqrt{n})\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 5e4 + 5;
int n, m;
ll prime[N], vis[N], mob[N]; int cnt;
ll sum[N], h[N];
void Solve() {
cin >> n >> m;
if (n > m) swap(n, m);
ll ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans += (sum[r] - sum[l - 1]) * h[n / l] * h[m / l];
}
cout << ans << '\n';
}
int main() {
sum[1] = mob[1] = 1;
for (int i = 2; i <= 5e4; i++) {
if (!vis[i]) {
vis[i] = i;
prime[++cnt] = i;
mob[i] = -1;
}
for (int j = 1; j <= cnt && prime[j] <= 5e4 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[prime[j] * i] = 0;
break;
} else {
mob[prime[j] * i] = -mob[i];
}
}
sum[i] = sum[i - 1] + mob[i];
}
for (int i = 1; i <= 5e4; i++) {
for (int l = 1, r; l <= i; l = r + 1) {
r = i / (i / l);
h[i] += (1ll * i / l) * (r - l + 1);
}
}
int T;
cin >> T;
while (T--) Solve();
return 0;
}
P1829 [国家集训队] Crash的数字表格 / JZPTAB
Solution
\(\sum_{i = 1}^{n} \sum_{j = 1}^{m} \frac{ij}{\gcd(i, j)}\)
\(= \sum_{d = 1}^{n} \sum_{i = 1}^{n} \sum_{j = 1}^{m} \frac{ij}{d}[\gcd(i, j) = d]\)
\(= \sum_{d = 1}^{n} \sum_{i = 1}^{\frac{n}{d}} \sum_{j = 1}^{\frac{m}{d}} \frac{ij}{d} \times d^2 \times [\gcd(i, j) = 1]\)
\(= \sum_{d = 1}^{n} \sum_{i = 1}^{\frac{n}{d}} \sum_{j = 1}^{\frac{m}{d}} ijd [\gcd(i, j) = 1]\)
\(= \sum_{d = 1}^{n} d \sum_{i = 1}^{\frac{n}{d}} \sum_{j = 1}^{\frac{m}{d}} ij \sum_{k \mid \gcd(i, j)} \mu(k)\)
\(= \sum_{d = 1}^{n} d \sum_{k = 1}^{\frac{n}{d}} \mu(k) \sum_{k \mid i, i \le \frac{n}{d}} i \sum_{k \mid j, j \le \frac{m}{d}} j\)
\(= \sum_{d = 1}^{n} d \sum_{k = 1}^{\frac{n}{d}} \mu(k) \sum_{i = 1}^{\frac{n}{dk}} ik \sum_{j = 1}^{\frac{m}{dk}} jk\)
设 \(a(x) = \sum_{i = 1}^{x} i\),
则 原式 \(= \sum_{d = 1}^{n} d \sum_{k = 1}^{\frac{n}{d}} \mu(k)k^2 a(\frac{n}{dk}) a(\frac{m}{dk})\),
设 \(f(n, m) = \sum_{k = 1}^{n} \mu(k)k^2 a(\frac{n}{k}) a(\frac{m}{k})\),
然后预处理 \(a\) 以及前缀和 \(\sum_{i = 1}^{n} \mu(i)i^2\),进行整除分块,即,
\(\sum_{d = 1}^{n} d \times f(\frac{n}{d}, \frac{m}{d})\),
发现最外层仍满足整除分块的性质,
于是嵌套两个整除分块即可,时间复杂度 \(O(n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 1e7 + 5;
const ll mod = 20101009;
int n, m;
ll prime[N], vis[N], mob[N]; int cnt;
ll sum[N], a[N];
ll calc(int n, int m) {
ll ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans = (ans + ((sum[r] - sum[l - 1] + mod) % mod) % mod * a[n / l] % mod * a[m / l] % mod) % mod;
}
return ans;
}
int main() {
sum[1] = mob[1] = 1;
for (int i = 2; i <= 1e7; i++) {
if (!vis[i]) {
vis[i] = i;
prime[++cnt] = i;
mob[i] = -1;
}
for (int j = 1; j <= cnt && prime[j] <= 1e7 / i; j++) {
vis[prime[j] * i] = prime[j];
if (i % prime[j] == 0) {
mob[prime[j] * i] = 0;
break;
} else {
mob[prime[j] * i] = -mob[i];
}
}
sum[i] = (sum[i - 1] + mob[i] * i % mod * i % mod) % mod;
}
for (int i = 1; i <= 1e7; i++) {
a[i] = (a[i - 1] + i) % mod;
}
cin >> n >> m;
if (n > m) swap(n, m);
ll ans = 0;
for (int l = 1, r; l <= n; l = r + 1) {
r = min(n / (n / l), m / (m / l));
ans = (ans + (a[r] - a[l - 1] + mod) % mod * calc(n / l, m / l) % mod) % mod;
}
cout << ans << '\n';
return 0;
}

浙公网安备 33010602011771号