Trong dãy 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 thì dễ: chỉ cần biết có bao nhiêu số là bội của .
Hàm Möbius là trọng số để chuyển từ cái dễ sang cái khó. Tổng bằng 1 khi và bằng 0 khi , nên cộng “số chia hết cho ” với trọng số giữ lại đúng các đối tượng có gcd bằng 1. Kèm với chia khối theo thương , các tổng này chạy trong mỗi truy vấn.
Hàm Möbius
; nếu chia hết cho bình phương một số nguyên tố; ngược lại với là số thừa số nguyên tố. Với có 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 thừa số, nên . Đây là toàn bộ bí mật: biểu thức thay được bằng , rồi đổi thứ tự tổng.
Sàng tuyến tính tính cho mọi số tới trong : 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 và số nguyên tố ước nguyên tố nhỏ nhất của : nếu thì và dừng; ngược lại .
Ví dụ đầu: số không chứa bình phương tới là , với chạy tới . Mỗi số được đếm với trọng số , bằng 1 đúng khi không chia hết cho bình phương nào.
Hình dưới là bảng từ 1 tới 48. Bấm một số: các ước của nó sáng lên, và tổng trên các ước bằng 0 trừ khi số đó là 1.
Ví dụĐếm Số Không Chứa Bình Phương
Đề. truy vấn, mỗi truy vấn . Đếm số không chứa bình phương trong .
Lời giải. Số không chứa bình phương tới là , nên cần tới . Duyệt số hạng cho mỗi truy vấn là phép với 100 truy vấn, quá chậm. Nhưng thương chỉ có giá trị khác nhau, và mọi từ tới cho cùng : gom cả khối, cộng lấy từ tổng tiền tố.
Bước 1: sàng tuyến tính . Mỗi hợp số được đánh dấu đúng một lần, bởi là ước nguyên tố nhỏ nhất của nó. Khi i % p == 0 thì , , và dừng: các số nguyên tố lớn hơn không còn là ước nhỏ nhất của . Ngược lại có thêm một thừa số nguyên tố mới, . mu lưu bằng signed char, pre bằng int, để hai mảng phần tử chỉ tốn khoảng 50 MB.
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à lớn nhất còn cho . 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.
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;
}
#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
1
7
19
61
Sàng một lần; mỗi truy vấn khối, khoảng vài chục nghìn với . Sàng ở đây phải chạy tới , và có hai cách tính :
Sàng tuyến tính nhanh hơn sàng điều hòa (với mỗi trừ khỏi mọi bội) khoảng 5 lần. Quy tắc: tính cho cỡ mười triệu thì dùng sàng tuyến tính.
Đếm theo bội
Gọi là số phần tử của dãy chia hết cho (tính bằng cách duyệt bội: với mỗi , cộng số lần xuất hiện của , tổng ). Số cặp có gcd chia hết cho là , số tập con khác rỗng là . Thay :
Cùng khuôn mẫu cho bộ ba, bộ bốn (), dãy có thứ tự trong hộp (), điểm nhìn thấy trên lưới.
Hỏi nhanh
Dãy . Theo công thức , có bao nhiêu tập con có gcd bằng 1?
, (số 2, 4), , . Tổng là , ứng với các tập .
Ví dụNhững Cặp Không Chung Ước
Đề. Dãy số tới . Đế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 là , với là số phần tử chia hết cho . Thay điều kiện bằng rồi đổi thứ tự tổng: số cặp nguyên tố cùng nhau là . Sàng tuyến tính tới như ví dụ trước.
Bước 1: qua tần suất. freq[x] là số lần giá trị xuất hiện; c cộng freq ở . Bỏ qua có , đỡ khoảng 40% số vòng. c * (c - 1) / 2 tới , phải là long long.
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;
}
#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
10
.
Ví dụTập Con Có GCD Bằng Một
Đề. Dãy số tới . Đếm tập chỉ số khác rỗng có gcd bằng 1, theo mô-đun .
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 là , nên đáp án là theo mô-đun. nên lũy thừa của 2 tính sẵn tới .
pw[0] = 1;
for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * 2 % MOD;
Số hạng có thể âm khi , nên cộng thêm MOD trước khi lấy dư.
ans = (ans + mu[d] * (pw[c] - 1) + MOD) % MOD;
#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
3
.
Gcd đúng bằng g
Có cách khác, không cần : trừ dần. Gọi là số đối tượng có gcd chia hết cho (dễ tính), là số có gcd đúng bằng . Vì , duyệt từ lớn xuống nhỏ:
Tổng hai vòng là . Cách này hợp khi cần cho mọi , như tính tổng gcd: .
Trong hình, duyệt từ lớn xuống: lấy trừ đi của mọi bội thật sự (xanh dương) đã tính xong.
Ví dụTổng GCD Của Mọi Dãy
Đề. Mọi dãy dài với phần tử trong , . Tính tổng gcd của chúng theo mô-đun .
Lời giải. Gọi là số dãy có gcd chia hết cho : mọi phần tử là bội của , mỗi phần tử có lựa chọn, nên . là số dãy có gcd đúng bằng . Vì , duyệt từ xuống 1 thì , với mọi đã tính xong. Đáp án là .
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: , có 9 dãy; , , , tổng .
#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
12
Vòng trừ bội , lũy thừa .
Chia khối theo thương
Số cặp , có là . Với nhiều truy vấn, mỗi truy vấn là quá chậm, nhưng chỉ có giá trị: với hiện tại, mọi tới cho cùng cặp thương, nên cộng cả khối bằng tổng tiền tố của trên . Mỗi truy vấn .
Trong hình, cột trên là , cột dưới là . Mỗi dải màu là một khối mà cả hai thương cùng không đổi.
Gcd đúng bằng trong hình chữ nhật : chia hai cạnh cho rồi đếm cặp nguyên tố cùng nhau. Đoạn thì bao hàm loại trừ bốn góc.
Hỏi nhanh
Với , nhận bao nhiêu giá trị khác nhau khi ?
Với có 10 giá trị ; với thương nhỏ hơn 10, cho thêm 9 giá trị . Tổng 19, cỡ .
Ví dụTruy Vấn Ước Chung Trên Hai Đoạn
Đề. truy vấn (tới ). Đếm cặp , có .
Lời giải. khi và chỉ khi , với . Vậy số cặp trong là , với đếm cặp nguyên tố cùng nhau. Đoạn lấy bằng bao hàm loại trừ bốn góc.
Bước 1: bằng chia khối. Đổi chỗ để , vì khi thì . e là 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].
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 bằng 0, và f(0, ·) = 0 vì vòng không chạy.
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);
#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
6
1
2
Mỗi truy vấn bốn lần f với khoảng khối, tổng khoảng phép nhẹ.
Khi đi thi
- “gcd bằng 1” hay “nguyên tố cùng nhau”: thay bằng , đổi thứ tự tổng, đếm theo bội . Gcd đúng bằng cho mọi : trừ từ trên xuống.
- Tổng dạng với nhiều truy vấn: tổng tiền tố và chia khối theo thương.
- Sàng tuyến tính tính , , ước nguyên tố nhỏ nhất cùng lúc trong .
Tóm tắt
- ; bằng 0 với số chứa bình phương, với tích số nguyên tố khác nhau.
- Số đối tượng có gcd bằng 1 là .
- cho gcd đúng bằng , .
- có giá trị; gom khối để mỗi truy vấn .
Bài tập
Đọc thêm
- Hàm MobiusVNOI Wiki
- 莫比乌斯反演OI Wiki
- Linear Sievecp-algorithms