学习笔记:莫比乌斯函数

前置知识:迪利克雷卷积,整除分块。


莫比乌斯函数

\(\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;
}
posted @ 2024-12-08 16:18  chenwenmo  阅读(157)  评论(0)    收藏  举报