Chương 7·Bài 70

Möbius Function

Trong dãy nn số, có bao nhiêu cặp nguyên tố cùng nhau? Đếm thẳng điều kiện “gcd bằng 1” thì khó. Nhưng đếm những cặp mà cả hai số cùng chia hết cho dd thì dễ: chỉ cần biết có bao nhiêu số là bội của dd.

Hàm Möbius μ\mu là trọng số để chuyển từ cái dễ sang cái khó. Tổng ∑d∣nμ(d)\sum_{d \mid n} \mu(d) bằng 1 khi n=1n = 1 và bằng 0 khi n>1n > 1, nên cộng “số chia hết cho dd” với trọng số μ(d)\mu(d) giữ lại đúng các đối tượng có gcd bằng 1. Kèm với chia khối theo thương ⌊n/d⌋\lfloor n/d \rfloor, các tổng này chạy trong O(n)O(\sqrt n) mỗi truy vấn.

Hàm Möbius

μ(1)=1\mu(1) = 1; μ(n)=0\mu(n) = 0 nếu nn chia hết cho bình phương một số nguyên tố; ngược lại μ(n)=(−1)k\mu(n) = (-1)^k với kk là số thừa số nguyên tố. Với n>1n > 1 có k≥1k \ge 1 thừa số nguyên tố phân biệt, các ước không chứa bình phương của nó là các tập con của kk thừa số, nên ∑d∣nμ(d)=∑j(kj)(−1)j=0\sum_{d \mid n} \mu(d) = \sum_j \binom{k}{j} (-1)^j = 0. Đây là toàn bộ bí mật: biểu thức [gcd⁡=1][\gcd = 1] thay được bằng ∑d∣gcd⁡μ(d)\sum_{d \mid \gcd} \mu(d), rồi đổi thứ tự tổng.

Sàng tuyến tính tính μ\mu cho mọi số tới NN trong O(N)O(N): mỗi hợp số bị đánh dấu đúng một lần bởi thừa số nguyên tố nhỏ nhất. Khi xét ii và số nguyên tố p≤p \le ước nguyên tố nhỏ nhất của ii: nếu p∣ip \mid i thì μ(ip)=0\mu(ip) = 0 và dừng; ngược lại μ(ip)=−μ(i)\mu(ip) = -\mu(i).

Ví dụ đầu: số không chứa bình phương tới NN là ∑dμ(d)⌊N/d2⌋\sum_{d} \mu(d) \lfloor N / d^2 \rfloor, với dd chạy tới N\sqrt N. Mỗi số xx được đếm với trọng số ∑d2∣xμ(d)\sum_{d^2 \mid x} \mu(d), bằng 1 đúng khi xx không chia hết cho bình phương nào.

Hình dưới là bảng μ\mu từ 1 tới 48. Bấm một số: các ước của nó sáng lên, và tổng μ\mu trên các ước bằng 0 trừ khi số đó là 1.

Bảng μ\mu và các ước.

Ví dụĐếm Số Không Chứa Bình Phương

Đề. T≤100T \le 100 truy vấn, mỗi truy vấn N≤1014N \le 10^{14}. Đếm số không chứa bình phương trong [1,N][1, N].

Lời giải. Số không chứa bình phương tới NN là ∑d≤Nμ(d)⌊N/d2⌋\sum_{d \le \sqrt N} \mu(d) \lfloor N / d^2 \rfloor, nên cần μ\mu tới 1014=107\sqrt{10^{14}} = 10^7. Duyệt 10710^7 số hạng cho mỗi truy vấn là 10910^9 phép với 100 truy vấn, quá chậm. Nhưng thương q=⌊N/d2⌋q = \lfloor N/d^2 \rfloor chỉ có O(N1/3)O(N^{1/3}) giá trị khác nhau, và mọi dd từ dd tới e=⌊N/q⌋e = \lfloor \sqrt{N/q} \rfloor cho cùng qq: gom cả khối, cộng q⋅∑d′=deμ(d′)q \cdot \sum_{d'=d}^{e} \mu(d') lấy từ tổng tiền tố.

Bước 1: sàng tuyến tính μ\mu. Mỗi hợp số i⋅pi \cdot p được đánh dấu đúng một lần, bởi pp là ước nguyên tố nhỏ nhất của nó. Khi i % p == 0 thì p2∣ipp^2 \mid ip, μ=0\mu = 0, và dừng: các số nguyên tố lớn hơn không còn là ước nhỏ nhất của i⋅p′i \cdot p'. Ngược lại ipip có thêm một thừa số nguyên tố mới, μ(ip)=−μ(i)\mu(ip) = -\mu(i). mu lưu bằng signed char, pre bằng int, để hai mảng 10710^7 phần tử chỉ tốn khoảng 50 MB.

C++
for (int i = 2; i < M; i++) {
    if (!comp[i]) primes.push_back(i), mu[i] = -1;
    for (int p : primes) {
        if ((ll)i * p >= M) break;
        comp[i * p] = 1;
        if (i % p == 0) {
            mu[i * p] = 0;
            break;
        }
        mu[i * p] = -mu[i];
    }
}

Bước 2: chia khối theo thương. e = isqrt(n / q) là dd lớn nhất còn cho ⌊N/d2⌋=q\lfloor N / d^2 \rfloor = q. isqrt là căn nguyên chính xác như bài 66, vì sqrtl có thể lệch 1 ở gần số chính phương.

C++
for (ll d = 1; d <= lim;) {
    ll q = n / (d * d), e = isqrt(n / q);
    ans += q * (pre[e] - pre[d - 1]);
    d = e + 1;
}
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int M = 1e7 + 5;
int pre[M];
signed char mu[M];
vector<int> primes;
bool comp[M];

ll isqrt(ll x) {
    ll r = sqrtl((long double)x);
    while (r * r > x) r--;
    while ((r + 1) * (r + 1) <= x) r++;
    return r;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    mu[1] = 1;
    for (int i = 2; i < M; i++) {
        if (!comp[i]) primes.push_back(i), mu[i] = -1;
        for (int p : primes) {
            if ((ll)i * p >= M) break;
            comp[i * p] = 1;
            if (i % p == 0) {
                mu[i * p] = 0;
                break;
            }
            mu[i * p] = -mu[i];
        }
    }
    for (int i = 1; i < M; i++) pre[i] = pre[i - 1] + mu[i];
    int T;
    cin >> T;
    while (T--) {
        ll n;
        cin >> n;
        ll lim = isqrt(n), ans = 0;
        for (ll d = 1; d <= lim;) {
            ll q = n / (d * d), e = isqrt(n / q);
            ans += q * (pre[e] - pre[d - 1]);
            d = e + 1;
        }
        cout << ans << '\n';
    }
}
4
1
10
30
100

Sàng O(N)O(\sqrt{N}) một lần; mỗi truy vấn O(N1/3)O(N^{1/3}) khối, khoảng vài chục nghìn với N=1014N = 10^{14}. Sàng ở đây phải chạy tới 10710^7, và có hai cách tính μ\mu:

Tính hàm Möbius của mười triệu số, trung vị 5 lần chạy, i7-10850H, g++ -O2.sàng điều hòa, N log N300 ms · gấp 4,9 lầnsàng tuyến tính62 ms
Tính hàm Möbius của mười triệu số, hai kiểu sàng, đo trên i7-10850H, g++ -O2.

Sàng tuyến tính nhanh hơn sàng điều hòa (với mỗi ii trừ μ(i)\mu(i) khỏi mọi bội) khoảng 5 lần. Quy tắc: tính μ\mu cho NN cỡ mười triệu thì dùng sàng tuyến tính.

Đếm theo bội

Gọi cdc_d là số phần tử của dãy chia hết cho dd (tính bằng cách duyệt bội: với mỗi dd, cộng số lần xuất hiện của d,2d,3d,…d, 2d, 3d, \dots, tổng O(Vlog⁡V)O(V \log V)). Số cặp có gcd chia hết cho dd là (cd2)\binom{c_d}{2}, số tập con khác rỗng là 2cd−12^{c_d} - 1. Thay [gcd⁡=1]=∑d∣gcd⁡μ(d)[\gcd = 1] = \sum_{d \mid \gcd} \mu(d):

#{cặp coˊ gcd 1}=∑dμ(d)(cd2),#{tập con coˊ gcd 1}=∑dμ(d) (2cd−1).\#\{\text{cặp có gcd } 1\} = \sum_{d} \mu(d) \binom{c_d}{2}, \qquad \#\{\text{tập con có gcd } 1\} = \sum_d \mu(d)\,(2^{c_d} - 1).

Cùng khuôn mẫu cho bộ ba, bộ bốn ((cd4)\binom{c_d}{4}), dãy có thứ tự trong hộp (⌊n/d⌋k\lfloor n/d \rfloor^k), điểm nhìn thấy trên lưới.

Hỏi nhanh

Dãy 2,3,42, 3, 4. Theo công thức ∑dμ(d)(2cd−1)\sum_d \mu(d)(2^{c_d} - 1), có bao nhiêu tập con có gcd bằng 1?

Ví dụNhững Cặp Không Chung Ước

Đề. Dãy n≤105n \le 10^5 số tới 10610^6. Đếm cặp chỉ số có hai giá trị nguyên tố cùng nhau.

Lời giải. Số cặp chỉ số có gcd chia hết cho dd là (cd2)\binom{c_d}{2}, với cdc_d là số phần tử chia hết cho dd. Thay điều kiện [gcd⁡=1][\gcd = 1] bằng ∑d∣gcd⁡μ(d)\sum_{d \mid \gcd} \mu(d) rồi đổi thứ tự tổng: số cặp nguyên tố cùng nhau là ∑dμ(d)(cd2)\sum_d \mu(d) \binom{c_d}{2}. Sàng tuyến tính μ\mu tới 10610^6 như ví dụ trước.

Bước 1: cdc_d qua tần suất. freq[x] là số lần giá trị xx xuất hiện; c cộng freq ở d,2d,3d,…d, 2d, 3d, \dots. Bỏ qua dd có μ(d)=0\mu(d) = 0, đỡ khoảng 40% số vòng. c * (c - 1) / 2 tới 5⋅1095 \cdot 10^9, phải là long long.

C++
for (int d = 1; d < V; d++) {
    if (!mu[d]) continue;
    ll c = 0;
    for (int k = d; k < V; k += d) c += freq[k];
    ans += mu[d] * c * (c - 1) / 2;
}
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int V = 1e6 + 5;
int n, mu[V], freq[V];
bool comp[V];
vector<int> primes;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    mu[1] = 1;
    for (int i = 2; i < V; i++) {
        if (!comp[i]) primes.push_back(i), mu[i] = -1;
        for (int p : primes) {
            if ((ll)i * p >= V) break;
            comp[i * p] = 1;
            if (i % p == 0) {
                mu[i * p] = 0;
                break;
            }
            mu[i * p] = -mu[i];
        }
    }
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        freq[x]++;
    }
    ll ans = 0;
    for (int d = 1; d < V; d++) {
        if (!mu[d]) continue;
        ll c = 0;
        for (int k = d; k < V; k += d) c += freq[k];
        ans += mu[d] * c * (c - 1) / 2;
    }
    cout << ans << '\n';
}
6
2 3 4 9 10 25

V(1+12+13+… )=O(Vlog⁡V)V (1 + \frac12 + \frac13 + \dots) = O(V \log V).

Ví dụTập Con Có GCD Bằng Một

Đề. Dãy n≤105n \le 10^5 số tới 10510^5. Đếm tập chỉ số khác rỗng có gcd bằng 1, theo mô-đun 109+710^9 + 7.

Lời giải. Cùng khuôn với ví dụ trước: số tập chỉ số khác rỗng mà mọi phần tử chia hết cho dd là 2cd−12^{c_d} - 1, nên đáp án là ∑dμ(d)(2cd−1)\sum_d \mu(d)(2^{c_d} - 1) theo mô-đun. cd≤nc_d \le n nên lũy thừa của 2 tính sẵn tới nn.

C++
pw[0] = 1;
for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 2 % MOD;

Số hạng có thể âm khi μ(d)=−1\mu(d) = -1, nên cộng thêm MOD trước khi lấy dư.

C++
ans = (ans + mu[d] * (pw[c] - 1) + MOD) % MOD;
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int V = 1e5 + 5;
const ll MOD = 1e9 + 7;
int n, mu[V], freq[V];
ll pw[V];
bool comp[V];
vector<int> primes;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    mu[1] = 1;
    for (int i = 2; i < V; i++) {
        if (!comp[i]) primes.push_back(i), mu[i] = -1;
        for (int p : primes) {
            if ((ll)i * p >= V) break;
            comp[i * p] = 1;
            if (i % p == 0) {
                mu[i * p] = 0;
                break;
            }
            mu[i * p] = -mu[i];
        }
    }
    cin >> n;
    pw[0] = 1;
    for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 2 % MOD;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        freq[x]++;
    }
    ll ans = 0;
    for (int d = 1; d < V; d++) {
        if (!mu[d]) continue;
        int c = 0;
        for (int k = d; k < V; k += d) c += freq[k];
        ans = (ans + mu[d] * (pw[c] - 1) + MOD) % MOD;
    }
    cout << ans << '\n';
}
3
2 3 4

O(Vlog⁡V)O(V \log V).

Gcd đúng bằng g

Có cách khác, không cần μ\mu: trừ dần. Gọi F(g)F(g) là số đối tượng có gcd chia hết cho gg (dễ tính), E(g)E(g) là số có gcd đúng bằng gg. Vì F(g)=∑k≥1E(kg)F(g) = \sum_{k \ge 1} E(kg), duyệt gg từ lớn xuống nhỏ:

E(g)=F(g)−∑k≥2E(kg).E(g) = F(g) - \sum_{k \ge 2} E(kg).

Tổng hai vòng là O(Klog⁡K)O(K \log K). Cách này hợp khi cần E(g)E(g) cho mọi gg, như tính tổng gcd: ∑gg⋅E(g)\sum_g g \cdot E(g).

Trong hình, duyệt gg từ lớn xuống: E(g)E(g) lấy F(g)F(g) trừ đi EE của mọi bội thật sự (xanh dương) đã tính xong.

Trừ dần từ gg lớn xuống.

Ví dụTổng GCD Của Mọi Dãy

Đề. Mọi dãy dài N≤105N \le 10^5 với phần tử trong [1,K][1, K], K≤105K \le 10^5. Tính tổng gcd của chúng theo mô-đun 109+710^9 + 7.

Lời giải. Gọi F(g)F(g) là số dãy có gcd chia hết cho gg: mọi phần tử là bội của gg, mỗi phần tử có ⌊K/g⌋\lfloor K/g \rfloor lựa chọn, nên F(g)=⌊K/g⌋NF(g) = \lfloor K/g \rfloor^N. E(g)E(g) là số dãy có gcd đúng bằng gg. Vì F(g)=∑k≥1E(kg)F(g) = \sum_{k \ge 1} E(kg), duyệt gg từ KK xuống 1 thì E(g)=F(g)−∑k≥2E(kg)E(g) = F(g) - \sum_{k \ge 2} E(kg), với mọi E(kg)E(kg) đã tính xong. Đáp án là ∑gg⋅E(g)\sum_g g \cdot E(g).

C++
for (ll g = k; g >= 1; g--) {
    E[g] = power(k / g, n);
    for (ll m = 2 * g; m <= k; m += g) E[g] = (E[g] - E[m] + MOD) % MOD;
    ans = (ans + g * E[g]) % MOD;
}

Kiểm với mẫu: N=2N = 2, K=3K = 3 có 9 dãy; E(3)=1E(3) = 1, E(2)=1E(2) = 1, E(1)=9−1−1=7E(1) = 9 - 1 - 1 = 7, tổng 7+2+3=127 + 2 + 3 = 12.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const ll MOD = 1e9 + 7;
ll n, k, E[100005];

ll power(ll a, ll b) {
    ll r = 1;
    a %= MOD;
    while (b) {
        if (b & 1) r = r * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return r;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> k;
    ll ans = 0;
    for (ll g = k; g >= 1; g--) {
        E[g] = power(k / g, n);
        for (ll m = 2 * g; m <= k; m += g) E[g] = (E[g] - E[m] + MOD) % MOD;
        ans = (ans + g * E[g]) % MOD;
    }
    cout << ans << '\n';
}
2 3

Vòng trừ bội O(Klog⁡K)O(K \log K), lũy thừa O(Klog⁡N)O(K \log N).

Chia khối theo thương

Số cặp x≤nx \le n, y≤my \le m có gcd⁡(x,y)=1\gcd(x, y) = 1 là ∑d≥1μ(d)⌊n/d⌋⌊m/d⌋\sum_{d \ge 1} \mu(d) \lfloor n/d \rfloor \lfloor m/d \rfloor. Với nhiều truy vấn, O(n)O(n) mỗi truy vấn là quá chậm, nhưng ⌊n/d⌋\lfloor n/d \rfloor chỉ có O(n)O(\sqrt n) giá trị: với dd hiện tại, mọi d′d' tới e=min⁡(n/⌊n/d⌋,m/⌊m/d⌋)e = \min(n / \lfloor n/d \rfloor, m / \lfloor m/d \rfloor) cho cùng cặp thương, nên cộng cả khối bằng tổng tiền tố của μ\mu trên [d,e][d, e]. Mỗi truy vấn O(n+m)O(\sqrt n + \sqrt m).

Trong hình, cột trên là ⌊n/d⌋\lfloor n/d \rfloor, cột dưới là ⌊m/d⌋\lfloor m/d \rfloor. Mỗi dải màu là một khối mà cả hai thương cùng không đổi.

Các khối cùng thương.

Gcd đúng bằng kk trong hình chữ nhật [1,n]×[1,m][1, n] \times [1, m]: chia hai cạnh cho kk rồi đếm cặp nguyên tố cùng nhau. Đoạn [a,b]×[c,d][a, b] \times [c, d] thì bao hàm loại trừ bốn góc.

Hỏi nhanh

Với n=100n = 100, ⌊100/d⌋\lfloor 100/d \rfloor nhận bao nhiêu giá trị khác nhau khi d=1..100d = 1..100?

Ví dụTruy Vấn Ước Chung Trên Hai Đoạn

Đề. T≤5⋅104T \le 5 \cdot 10^4 truy vấn a,b,c,d,ka, b, c, d, k (tới 5⋅1045 \cdot 10^4). Đếm cặp x∈[a,b]x \in [a, b], y∈[c,d]y \in [c, d] có gcd⁡(x,y)=k\gcd(x, y) = k.

Lời giải. gcd⁡(x,y)=k\gcd(x, y) = k khi và chỉ khi x=kx′x = k x', y=ky′y = k y' với gcd⁡(x′,y′)=1\gcd(x', y') = 1. Vậy số cặp trong [1,n]×[1,m][1, n] \times [1, m] là f(⌊n/k⌋,⌊m/k⌋)f(\lfloor n/k \rfloor, \lfloor m/k \rfloor), với f(n,m)=∑dμ(d)⌊n/d⌋⌊m/d⌋f(n, m) = \sum_d \mu(d) \lfloor n/d \rfloor \lfloor m/d \rfloor đếm cặp nguyên tố cùng nhau. Đoạn [a,b]×[c,d][a, b] \times [c, d] lấy bằng bao hàm loại trừ bốn góc.

Bước 1: ff bằng chia khối. Đổi chỗ để n≤mn \le m, vì khi d>nd > n thì ⌊n/d⌋=0\lfloor n/d \rfloor = 0. e là dd lớn nhất mà cả hai thương không đổi; cả khối cộng một lần bằng pre[e] - pre[d - 1].

C++
ll f(ll n, ll m) {
    if (n > m) swap(n, m);
    ll res = 0;
    for (ll d = 1; d <= n;) {
        ll e = min(n / (n / d), m / (m / d));
        res += (ll)(pre[e] - pre[d - 1]) * (n / d) * (m / d);
        d = e + 1;
    }
    return res;
}

Bước 2: bốn góc. (a - 1) / k với a=1a = 1 bằng 0, và f(0, ·) = 0 vì vòng không chạy.

C++
ll ans = f(b / k, d / k) - f((a - 1) / k, d / k) - f(b / k, (c - 1) / k) + f((a - 1) / k, (c - 1) / k);
Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int V = 5e4 + 5;
int mu[V], pre[V];
bool comp[V];
vector<int> primes;

ll f(ll n, ll m) {
    if (n > m) swap(n, m);
    ll res = 0;
    for (ll d = 1; d <= n;) {
        ll e = min(n / (n / d), m / (m / d));
        res += (ll)(pre[e] - pre[d - 1]) * (n / d) * (m / d);
        d = e + 1;
    }
    return res;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    mu[1] = 1;
    for (int i = 2; i < V; i++) {
        if (!comp[i]) primes.push_back(i), mu[i] = -1;
        for (int p : primes) {
            if ((ll)i * p >= V) break;
            comp[i * p] = 1;
            if (i % p == 0) {
                mu[i * p] = 0;
                break;
            }
            mu[i * p] = -mu[i];
        }
    }
    for (int i = 1; i < V; i++) pre[i] = pre[i - 1] + mu[i];
    int T;
    cin >> T;
    while (T--) {
        ll a, b, c, d, k;
        cin >> a >> b >> c >> d >> k;
        ll ans = f(b / k, d / k) - f((a - 1) / k, d / k) - f(b / k, (c - 1) / k) + f((a - 1) / k, (c - 1) / k);
        cout << ans << '\n';
    }
}
3
2 4 3 5 1
2 4 3 5 2
4 6 6 8 2

Mỗi truy vấn bốn lần f với khoảng 25⋅104≈4502\sqrt{5 \cdot 10^4} \approx 450 khối, tổng khoảng 10810^8 phép nhẹ.

Khi đi thi

  • “gcd bằng 1” hay “nguyên tố cùng nhau”: thay bằng ∑d∣gcd⁡μ(d)\sum_{d \mid \gcd} \mu(d), đổi thứ tự tổng, đếm theo bội cdc_d. Gcd đúng bằng gg cho mọi gg: trừ từ trên xuống.
  • Tổng dạng ∑dμ(d)⌊n/d⌋⌊m/d⌋\sum_d \mu(d) \lfloor n/d \rfloor \lfloor m/d \rfloor với nhiều truy vấn: tổng tiền tố μ\mu và chia khối theo thương.
  • Sàng tuyến tính tính μ\mu, φ\varphi, ước nguyên tố nhỏ nhất cùng lúc trong O(N)O(N).

Tóm tắt

  • ∑d∣nμ(d)=[n=1]\sum_{d \mid n} \mu(d) = [n = 1]; μ\mu bằng 0 với số chứa bình phương, (−1)k(-1)^k với tích kk số nguyên tố khác nhau.
  • Số đối tượng có gcd bằng 1 là ∑dμ(d)⋅(soˆˊ đoˆˊi tượng toaˋn bội của d)\sum_d \mu(d) \cdot (\text{số đối tượng toàn bội của } d).
  • E(g)=F(g)−∑k≥2E(kg)E(g) = F(g) - \sum_{k \ge 2} E(kg) cho gcd đúng bằng gg, O(Klog⁡K)O(K \log K).
  • ⌊n/d⌋\lfloor n/d \rfloor có O(n)O(\sqrt n) giá trị; gom khối để mỗi truy vấn O(n)O(\sqrt n).

Bài tập

Đọc thêm