Chương 0·Bài 08

Arithmetic

Nhiều bài thi cho một con số rất lớn rồi hỏi về cấu trúc của nó: N≤1012N \le 10^{12} có bao nhiêu ước, ab mod pa^b \bmod p với bb cỡ 101810^{18}, hay có bao nhiêu số nguyên tố không vượt 10810^8. Làm đúng theo định nghĩa thì không kịp: thử chia cho mọi số tới NN là 101210^{12} phép chia, nhân aa với chính nó bb lần là 101810^{18} phép nhân. Số học trong thi đấu là vài công cụ biến các vòng lặp đó thành O(N)O(\sqrt N), O(Nlog⁡log⁡N)O(N \log\log N) hay O(log⁡b)O(\log b): thuật toán Euclid, thử chia tới căn, sàng Eratosthenes, lũy thừa nhanh và nghịch đảo theo modulo.

Mỗi công cụ chỉ vài dòng code, nhưng chỗ sai thì luôn quen thuộc: phần dư âm, tràn số, sót thừa số nguyên tố cuối cùng, i * i vượt int khi sàng. Bài này đi qua từng công cụ cùng lý do nó đúng. Các bài 47 (Euclid mở rộng), 54 (định lý thặng dư Trung Hoa) và 70 (hàm Mobius) xây tiếp trên nền này.

Phần dư và chia hết

Với số nguyên aa và m>0m > 0 có đúng một cặp (q,r)(q, r) thỏa a=qm+ra = qm + r và 0≤r<m0 \le r < m. Số rr là phần dư, viết r=a mod mr = a \bmod m; khi r=0r = 0 ta nói aa chia hết cho mm, viết m∣am \mid a. Ba phép ++, −-, ×\times đi qua phần dư được:

(a+b) mod m=((a mod m)+(b mod m)) mod m,(a + b) \bmod m = \big((a \bmod m) + (b \bmod m)\big) \bmod m,

và tương tự cho phép trừ, phép nhân, vì mọi phần chứa mm biến mất khi lấy phần dư. Nhờ vậy ta lấy dư sau từng phép tính thay vì dồn tới cuối. Phép chia thì không đi qua được; mục nghịch đảo bên dưới xử lý riêng.

Chỗ vấp đầu tiên nằm ở C++: % cắt thương về phía 0 nên phần dư mang dấu của số bị chia, -7 % 5 bằng -2, trong khi phần dư toán học là 33. Muốn phần dư luôn trong [0,m)[0, m) thì viết (a % m + m) % m; phép trừ hai phần dư viết (x - y + m) % m. Chỗ vấp thứ hai là tràn số: tích hai phần dư lên tới m2≈1018m^2 \approx 10^{18} với m=109+7m = 10^9 + 7, vừa long long nhưng chỉ khi thừa số đã là long long. Với mm vượt khoảng 3⋅1093 \cdot 10^9, nhân trung gian bằng __int128.

Hỏi nhanh

Trong C++, với a=−7a = -7 và m=5m = 5 thì a % m bằng −2-2. Biểu thức nào cho phần dư theo định nghĩa toán học, nằm trong [0,m)[0, m), đúng với mọi số nguyên aa?

Ví dụBước Tới Bội Số

Đề. t≤104t \le 10^4 bộ (a,b)(a, b), a,b≤109a, b \le 10^9. Mỗi thao tác tăng aa thêm 1; ít nhất bao nhiêu thao tác để aa chia hết cho bb?

Lời giải. Gọi r=a mod br = a \bmod b. Bội kế tiếp của bb không nhỏ hơn aa là aa nếu r=0r = 0, và a+(b−r)a + (b - r) nếu r>0r > 0. Chỗ dễ sai là in b−rb - r cả khi r=0r = 0, cho bb thay vì 00.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll t, a, b;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> t;
    while (t--) {
        cin >> a >> b;
        ll r = a % b;
        cout << (r == 0 ? 0 : b - r) << '\n';
    }
}
4
8 4
8 5
1 9
999999999 1000000000

Ước chung lớn nhất và bội chung nhỏ nhất

Thuật toán Euclid tính ƯCLN, ký hiệu gcd⁡(a,b)\gcd(a, b), dựa vào một nhận xét: dd chia hết cả aa lẫn bb khi và chỉ khi dd chia hết cả bb lẫn r=a mod br = a \bmod b, vì r=a−qbr = a - qb và a=r+qba = r + qb. Hai cặp có cùng tập ước chung, nên

gcd⁡(a,b)=gcd⁡(b,a mod b),gcd⁡(a,0)=a.\gcd(a, b) = \gcd(b, a \bmod b), \qquad \gcd(a, 0) = a.

Với (252,105)(252, 105): 252=2⋅105+42252 = 2 \cdot 105 + 42, 105=2⋅42+21105 = 2 \cdot 42 + 21, 42=2⋅2142 = 2 \cdot 21, nên gcd⁡=21\gcd = 21. Hình học của phép tính là lát hình chữ nhật bằng hình vuông lớn nhất có thể, cạnh hình vuông cuối cùng là ƯCLN.

Mỗi bước cắt đi các hình vuông lớn nhất có thể; hình vuông cuối cùng có cạnh là ƯCLN.

Số bước ít vì phần dư giảm rất nhanh: với a≥ba \ge b ta có a mod b<a/2a \bmod b < a/2 (nếu b≤a/2b \le a/2 thì phần dư nhỏ hơn bb, còn nếu b>a/2b > a/2 thì phần dư là a−ba - b). Cứ hai bước số lớn giảm ít nhất một nửa, nên có O(log⁡min⁡(a,b))O(\log \min(a, b)) bước, khoảng 85 phép chia với giá trị tới 101810^{18}.

C++
ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

Bội chung nhỏ nhất, ký hiệu lcm⁡(a,b)\operatorname{lcm}(a, b), đi theo ƯCLN: khi phân tích ra nguyên tố, gcd⁡\gcd lấy số mũ nhỏ hơn còn lcm⁡\operatorname{lcm} lấy số mũ lớn hơn, nên gcd⁡⋅lcm⁡=ab\gcd \cdot \operatorname{lcm} = ab và lcm⁡(a,b)=agcd⁡(a,b)⋅b\operatorname{lcm}(a, b) = \frac{a}{\gcd(a, b)} \cdot b. Chia trước nhân sau là cố ý: mọi số trung gian không vượt kết quả. C++17 có sẵn __gcd và gcd. ƯCLN cả dãy thì gộp dần g←gcd⁡(g,ai)g \leftarrow \gcd(g, a_i) từ g=0g = 0; hai số nguyên dương liên tiếp luôn nguyên tố cùng nhau vì gcd⁡(n,n+1)=gcd⁡(n,1)=1\gcd(n, n + 1) = \gcd(n, 1) = 1.

Ví dụNhịp Chung Sớm Nhất

Đề. Hai thiết bị phát tín hiệu sau mỗi AA và BB đơn vị thời gian (A,B≤105A, B \le 10^5), cùng phát lúc 00. Tìm thời điểm dương sớm nhất hai thiết bị lại cùng phát.

Lời giải. Thời điểm cùng phát là bội chung của AA và BB, sớm nhất là BCNN. Tích ABAB tới 101010^{10} vượt int, nên dùng long long và chia trước nhân sau.

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

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

signed main() {
    cin >> a >> b;
    cout << a / gcd(a, b) * b << '\n';
}
12 18

Số nguyên tố, thừa số và ước

Mọi số n>1n > 1 viết được duy nhất thành n=p1e1⋯pkekn = p_1^{e_1} \cdots p_k^{e_k} với pip_i nguyên tố đôi một khác nhau. Mọi câu hỏi về ước, về chia hết, về chính phương đều đọc được từ biểu diễn này.

Kiểm tra nguyên tố. Hợp số n=d⋅en = d \cdot e với 1<d≤e1 < d \le e thì d2≤nd^2 \le n, nên chỉ cần thử d=2,3,…d = 2, 3, \dots trong khi d2≤nd^2 \le n. Viết d * d <= n với long long, đừng qua số thực sqrt. Với n≤1012n \le 10^{12} là 10610^6 lần chia.

Phân tích thừa số. Chạy đúng vòng lặp ấy, mỗi khi dd chia hết số còn lại thì chia sạch và ghi số mũ. Mọi dd chia được đều nguyên tố, vì thừa số nguyên tố nhỏ hơn của nó đã bị chia sạch từ trước. Vòng lặp dừng khi d2d^2 vượt số còn lại; lúc đó nếu số còn lại lớn hơn 1 thì nó là một nguyên tố với số mũ 1. Thừa số cuối này hay bị quên. Ví dụ 360=23⋅32⋅5360 = 2^3 \cdot 3^2 \cdot 5: chia hết 2 ba lần, 3 hai lần, còn 55, tới d=4d = 4 thì 16>516 > 5, thêm 55.

C++
for (ll d = 2; d * d <= n; d++) {
    if (n % d) continue;
    ll e = 0;
    while (n % d == 0) n /= d, e++;
    p[++k] = d, ex[k] = e;
}
if (n > 1) p[++k] = n, ex[k] = 1;

Đếm ước. Mỗi ước chọn độc lập số mũ từ 00 tới eie_i cho từng nguyên tố, nên số ước của nn, ký hiệu τ(n)\tau(n), bằng (e1+1)(e2+1)⋯(ek+1)(e_1 + 1)(e_2 + 1) \cdots (e_k + 1); 360360 có 4⋅3⋅2=244 \cdot 3 \cdot 2 = 24 ước. Số ước nhỏ hơn nn rất nhiều: tối đa 1344 khi n≤109n \le 10^9, 6720 khi n≤1012n \le 10^{12}. Liệt kê ước: các ước đi theo cặp (d,n/d)(d, n/d) với một ước không vượt n\sqrt n; duyệt dd tới căn, lấy cả hai (một lần nếu bằng nhau), rồi sắp xếp. Số ước lẻ chỉ khi nn là số chính phương.

Mỗi ước d không vượt √n đi cặp với n/d; đổi n để xem.

Hỏi nhanh

Chạy đoạn phân tích thừa số trên với n=14n = 14: vòng for chia hết 22 rồi dừng, phần còn lại là 77. Với nn bất kỳ, nếu vòng lặp dừng mà phần còn lại lớn hơn 1 thì ta kết luận được gì?

Ví dụLũy Thừa Chưa Dùng

Đề. Cho N≤1012N \le 10^{12}. Mỗi thao tác chọn z=pez = p^e (pp nguyên tố, e≥1e \ge 1) chia hết giá trị hiện tại của NN và chưa từng được chọn, rồi chia NN cho zz. Tìm số thao tác lớn nhất.

Lời giải. Phân tích NN thành tích lũy thừa các số nguyên tố, nguyên tố pp có số mũ EpE_p. Các thao tác dùng nguyên tố pp chọn các số mũ đôi một khác nhau có tổng không vượt EpE_p, và các nguyên tố không tranh nhau gì, nên bài tách theo từng nguyên tố. Với số mũ EE, kk số nguyên dương phân biệt có tổng ít nhất 1+2+⋯+k1 + 2 + \dots + k, nên chọn các số mũ 1,2,3,…1, 2, 3, \dots chừng nào còn đủ là tối ưu. Kiểm với 72=23⋅3272 = 2^3 \cdot 3^2: số mũ 3 cho hai thao tác (22 và 44), số mũ 2 cho một (33), tổng 3.

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

void take(ll e) {
    for (ll k = 1; e >= k; k++) {
        e -= k;
        ans++;
    }
}

signed main() {
    cin >> n;
    for (ll d = 2; d * d <= n; d++) {
        if (n % d) continue;
        ll e = 0;
        while (n % d == 0) n /= d, e++;
        take(e);
    }
    if (n > 1) take(1);
    cout << ans << '\n';
}
72

Sàng Eratosthenes

Cần biết số nào trong 1..N1..N là nguyên tố, với NN cỡ 10710^7. Thử chia từng số tốn tổng cỡ NNN\sqrt N. Sàng Eratosthenes lật ngược cách hỏi: thay vì mỗi số đi tìm ước, mỗi số nguyên tố tự gạch các bội của nó. Duyệt p=2,3,…p = 2, 3, \dots; nếu pp chưa bị gạch thì pp nguyên tố, và gạch p2,p2+p,…p^2, p^2 + p, \dots. Bắt đầu từ p2p^2 vì bội kpkp với k<pk < p đã bị gạch bởi ước nguyên tố của kk; chỉ cần p≤Np \le \sqrt N vì hợp số x≤Nx \le N có ước nguyên tố không quá N\sqrt N.

Ô cam là số nguyên tố đang dùng để gạch, ô đỏ là bội vừa gạch ở bước này.
C++
for (ll i = 2; i * i <= n; i++)
    if (!comp[i])
        for (ll j = i * i; j <= n; j += i) comp[j] = true;

Mỗi nguyên tố pp gạch khoảng N/pN/p số, cộng lại chỉ cỡ Nlog⁡log⁡NN \log \log N phép, gần như tuyến tính: N=107N = 10^7 chừng 50 ms. Mảng bool tốn NN byte; bitset hoặc vector<bool> chỉ N/8N/8 byte khi NN cỡ 10810^8.

Hỏi nhanh

Một bạn sàng tới n=107n = 10^7 nhưng cho vòng ngoài chạy for (int i = 2; i <= n; i++), và nếu i chưa bị gạch thì vòng trong bắt đầu for (int j = i * i; j <= n; j += i). Mọi biến đều kiểu int. Điều gì xảy ra?

Khi RR tới 10910^9 nhưng chỉ hỏi trên một đoạn [L,R][L, R] ngắn cỡ 10610^6 số, ta sàng đoạn (segmented sieve): dùng các nguyên tố p≤Rp \le \sqrt R, mỗi pp gạch từ bội đầu tiên của nó trong đoạn, nhưng không sớm hơn p2p^2.

Ví dụBa Ước Dương

Đề. n≤105n \le 10^5 số xi≤1012x_i \le 10^{12}. Với mỗi số, có đúng ba ước dương không?

Lời giải. τ(x)=(e1+1)(e2+1)⋯=3\tau(x) = (e_1 + 1)(e_2 + 1) \cdots = 3 chỉ khi xx có đúng một nguyên tố với số mũ 2, tức x=p2x = p^2. Vậy cần: xx là số chính phương và căn của nó là nguyên tố. Căn tới 10610^6 nên sàng tới 10610^6 một lần, mỗi truy vấn tính căn nguyên đúng rồi tra sàng. Căn số thực có thể lệch một đơn vị ở cỡ 101210^{12}, nên sửa lại bằng hai vòng while.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll M = 1e6;
ll n;
bool comp[M + 5];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    comp[0] = comp[1] = true;
    for (ll i = 2; i * i <= M; i++)
        if (!comp[i])
            for (ll j = i * i; j <= M; j += i) comp[j] = true;
    cin >> n;
    while (n--) {
        ll x;
        cin >> x;
        ll r = sqrtl(x);
        while (r * r > x) r--;
        while ((r + 1) * (r + 1) <= x) r++;
        cout << (r * r == x && !comp[r] ? "YES" : "NO") << '\n';
    }
}
6
4 5 9 16 25 49

Ước nguyên tố nhỏ nhất và đếm theo bội

Có q=106q = 10^6 số x≤106x \le 10^6, mỗi số cần phân tích. Thử chia từng số tốn tổng cỡ 10910^9. Sàng sẵn spf[x]\mathrm{spf}[x], ước nguyên tố nhỏ nhất (smallest prime factor) của mọi x≤Mx \le M: duyệt ii tăng dần, nếu spf[i]\mathrm{spf}[i] chưa có thì ii nguyên tố và gán ii cho mọi bội còn trống. Phân tích xx khi đó chỉ là chia liên tiếp cho spf[x]\mathrm{spf}[x], không quá log⁡2x\log_2 x bước.

C++
for (ll i = 2; i <= M; i++)
    if (!spf[i])
        for (ll j = i; j <= M; j += i)
            if (!spf[j]) spf[j] = i;

Muốn số ước τ(x)\tau(x) hay tổng các ước σ(x)\sigma(x) cho mọi x≤Mx \le M cùng lúc, ta đảo vòng lặp: thay vì hỏi “số xx có những ước nào”, hỏi “ước dd nằm trong những số nào”, tức các bội d,2d,3d,…d, 2d, 3d, \dots. Số phép tính là M/1+M/2+⋯+M/MM/1 + M/2 + \dots + M/M, khoảng 1,4⋅1071{,}4 \cdot 10^7 với M=106M = 10^6.

Hàm phi Euler φ(n)\varphi(n) là số các kk với 1≤k≤n1 \le k \le n nguyên tố cùng nhau với nn, bằng n(1−1/p1)(1−1/p2)⋯n (1 - 1/p_1)(1 - 1/p_2) \cdots, với p1,p2,…p_1, p_2, \dots là các ước nguyên tố của nn. Sàng tương ứng: đặt φ[x]=x\varphi[x] = x, rồi với mỗi nguyên tố pp và mỗi bội xx của pp thì φ[x]−=φ[x]/p\varphi[x] \mathrel{-}= \varphi[x]/p. Khi MM cỡ 10710^7 trở lên, sàng tuyến tính gạch mỗi hợp số đúng một lần bởi ước nguyên tố nhỏ nhất của nó, chạy trong O(M)O(M).

Ví dụĐếm Ước Cho Nhiều Truy Vấn

Đề. q≤105q \le 10^5 truy vấn, mỗi truy vấn một số x≤106x \le 10^6; in số ước dương của xx.

Lời giải. Đảo vòng lặp: mỗi dd cộng 1 vào mọi bội của nó, dựng cnt[x] cho mọi x≤106x \le 10^6 trong khoảng 1,4⋅1071{,}4 \cdot 10^7 phép; mỗi truy vấn là một lần đọc mảng.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll M = 1e6;
ll q, cnt[M + 5];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    for (ll d = 1; d <= M; d++)
        for (ll x = d; x <= M; x += d) cnt[x]++;
    cin >> q;
    while (q--) {
        ll x;
        cin >> x;
        cout << cnt[x] << '\n';
    }
}
5
1
36
17
1000000
72

Lũy thừa nhanh theo modulo

Tính ab mod ma^b \bmod m với bb tới 101810^{18}. Viết bb ở hệ nhị phân: 13=11012=8+4+113 = 1101_2 = 8 + 4 + 1 nên a13=a8⋅a4⋅aa^{13} = a^8 \cdot a^4 \cdot a. Các lũy thừa a2ia^{2^i} có từ nhau bằng bình phương, nên chỉ cần khoảng log⁡2b\log_2 b phép bình phương và nhiều nhất log⁡2b\log_2 b phép nhân vào kết quả.

Cột k là bit thứ k của số mũ, tính từ bit thấp; chỉ bit 1 mới nhân vào res.
C++
ll power(ll a, ll b, ll m) {
    ll res = 1 % m;
    a %= m;
    while (b) {
        if (b & 1) res = res * a % m;
        a = a * a % m;
        b >>= 1;
    }
    return res;
}

Vòng lặp giữ bất biến res * a^b luôn bằng đáp án: bb lẻ thì chuyển một thừa số aa sang res; rồi ab=(a2)b/2a^b = (a^2)^{b/2}. Khởi tạo 1 % m vì modulo 1 thì mọi phần dư là 0. Cùng khung “bình phương rồi nhân theo bit” dùng được cho mọi phép nhân kết hợp được, như nhân ma trận để tính Fibonacci chỉ số 101810^{18} (bài 18).

Ví dụLũy Thừa Nhanh Theo Mô-đun

Đề. q≤2⋅105q \le 2 \cdot 10^5 truy vấn (a,b)(a, b) với 0≤a,b≤1090 \le a, b \le 10^9; in ab mod (109+7)a^b \bmod (10^9 + 7), quy ước a0=1a^0 = 1 kể cả a=0a = 0.

Lời giải. Đúng khuôn trên, mỗi truy vấn khoảng 30 vòng. Với b=0b = 0 vòng lặp không chạy và trả về 1, đúng quy ước. a %= m trước khi bình phương để aa cỡ 10910^9 không tràn.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1e9 + 7;
ll q, a, b;

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

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> q;
    while (q--) {
        cin >> a >> b;
        cout << power(a, b) << '\n';
    }
}
5
2 10
3 0
0 0
0 5
1000000000 2

Phép chia theo modulo: nghịch đảo, Fermat và tổ hợp

Phép chia không đi qua phần dư: 124=3\frac{12}{4} = 3, nhưng theo modulo 5 hai số này là 22 và 44, và 24\frac{2}{4} không nguyên. Lối thoát là thay “chia cho bb” bằng “nhân với nghịch đảo” b−1b^{-1}, số mà b⋅b−1b \cdot b^{-1} chia cho mm dư 1. Từ đây ta viết x≡y(modm)x \equiv y \pmod m (đọc là xx đồng dư yy theo modulo mm) khi xx và yy có cùng phần dư khi chia cho mm, nên điều kiện trên là b⋅b−1≡1(modm)b \cdot b^{-1} \equiv 1 \pmod m. Nghịch đảo tồn tại khi và chỉ khi gcd⁡(b,m)=1\gcd(b, m) = 1 (bài 47); với m=pm = p nguyên tố, mọi bb không chia hết cho pp đều có.

Định lý Fermat nhỏ: nếu bb không chia hết cho pp (viết p∤bp \nmid b) thì bp−1≡1(modp)b^{p-1} \equiv 1 \pmod p. Chứng minh ngắn: các số b,2b,…,(p−1)bb, 2b, \dots, (p-1)b đôi một khác phần dư và đều khác 00, nên là một hoán vị của 1,…,p−11, \dots, p - 1; nhân tất cả lại rồi giản ước (p−1)!(p-1)!. Suy ra

b−1≡bp−2(modp),b^{-1} \equiv b^{p-2} \pmod p,

tính bằng lũy thừa nhanh. Để tính tổ hợp (nk)=n!k!(n−k)!\binom{n}{k} = \frac{n!}{k!(n-k)!} cho nhiều câu hỏi, tiền xử lý fact[i] và inv[i] =(i!)−1= (i!)^{-1}: một lần lũy thừa cho inv[N], rồi đi lùi inv[i - 1] = inv[i] * i. Mỗi câu hỏi O(1)O(1). Cách này cần n<pn < p và pp nguyên tố.

Fermat còn cho hệ quả: nếu p∤ap \nmid a thì

ae≡a e mod (p−1)(modp).a^e \equiv a^{\,e \bmod (p-1)} \pmod p.

Số mũ chỉ cần biết modulo p−1p - 1, không phải modulo pp. Nhờ đó một số mũ khổng lồ như bcb^c vẫn xử lý được. Nếu p∣ap \mid a thì ae≡0a^e \equiv 0 với mọi e≥1e \ge 1, công thức không áp dụng.

Hỏi nhanh

Tính 3(2100) mod 73^{(2^{100})} \bmod 7. Số mũ 21002^{100} quá lớn nên phải rút gọn trước. Kết quả là bao nhiêu?

Ví dụLũy Thừa Hai Tầng

Đề. q≤105q \le 10^5 bộ (a,b,c)(a, b, c) với 0≤a,b,c≤1090 \le a, b, c \le 10^9. Tính abc mod (109+7)a^{b^c} \bmod (10^9 + 7), quy ước 00=10^0 = 1 ở cả hai tầng.

Lời giải. Số mũ E=bcE = b^c có thể có cỡ 101010^{10} chữ số. Đặt P=109+7P = 10^9 + 7; vì a<Pa < P, P∣aP \mid a chỉ khi a=0a = 0. Ba trường hợp: nếu b=0b = 0 và c>0c > 0 thì E=0E = 0, đáp án là 11 kể cả khi a=0a = 0; ngược lại E≥1E \ge 1, nên a=0a = 0 cho 00; còn lại tính e=bc mod (P−1)e = b^c \bmod (P - 1) rồi in ae mod Pa^e \bmod P. Nếu e=0e = 0 trong khi E≥1E \ge 1 thì (P−1)∣E(P-1) \mid E và aE≡1=a0a^E \equiv 1 = a^0, vẫn khớp. Lỗi hay gặp nhất là rút gọn số mũ theo PP thay vì P−1P - 1.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll P = 1e9 + 7;
ll q, a, b, c;

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

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> q;
    while (q--) {
        cin >> a >> b >> c;
        if (b == 0 && c > 0) cout << 1 << '\n';
        else if (a == 0) cout << 0 << '\n';
        else cout << power(a, power(b, c, P - 1), P) << '\n';
    }
}
5
2 3 2
5 0 7
0 0 0
0 2 3
7 10 0

Đếm bằng phần nguyên: Legendre và gộp khối

Số bội của dd trong 1..N1..N là ⌊N/d⌋\lfloor N/d \rfloor. Từ đây có hai công cụ.

Công thức Legendre. Số mũ của nguyên tố pp trong n!n!, ký hiệu vp(n!)v_p(n!), là

vp(n!)=⌊np⌋+⌊np2⌋+⌊np3⌋+⋯ ,v_p(n!) = \left\lfloor \frac{n}{p} \right\rfloor + \left\lfloor \frac{n}{p^2} \right\rfloor + \left\lfloor \frac{n}{p^3} \right\rfloor + \cdots,

vì có ⌊n/pi⌋\lfloor n/p^i \rfloor số x≤nx \le n chia hết pip^i, mỗi số góp một thừa số pp cho lũy thừa ấy. v5(100!)=20+4=24v_5(100!) = 20 + 4 = 24. Vòng lặp chỉ cần chia dần: for (ll q = n / p; q; q /= p) s += q;.

Hỏi nhanh

100!100! có bao nhiêu chữ số 00 ở cuối khi viết trong hệ thập phân?

Gộp khối. Tổng ⌊N/1⌋+⌊N/2⌋+⋯+⌊N/N⌋\lfloor N/1 \rfloor + \lfloor N/2 \rfloor + \dots + \lfloor N/N \rfloor với N=1012N = 10^{12} có quá nhiều số hạng, nhưng thương ⌊N/d⌋\lfloor N/d \rfloor chỉ có O(N)O(\sqrt N) giá trị khác nhau: với d≤Nd \le \sqrt N có không quá N\sqrt N giá trị của dd, còn với d>Nd > \sqrt N thương nhỏ hơn N\sqrt N. Mỗi giá trị chiếm một khối dd liên tiếp; khối bắt đầu từ ll kết thúc ở

r=⌊N⌊N/l⌋⌋.r = \left\lfloor \frac{N}{\lfloor N/l \rfloor} \right\rfloor .

Với N=10N = 10, các thương 10,5,3,2,2,1,1,1,1,110, 5, 3, 2, 2, 1, 1, 1, 1, 1 chia thành năm khối. Trong mỗi khối thương không đổi, nên những tổng như tổng các d⋅⌊N/d⌋d \cdot \lfloor N/d \rfloor chỉ còn là tổng cấp số cộng l+⋯+rl + \dots + r nhân với thương.

Kéo n: số khối luôn cỡ 2√n, dù số cột là n.
C++
for (ll l = 1, r; l <= n; l = r + 1) {
    r = n / (n / l);
    ans += (r - l + 1) * (n / l);
}

Ví dụTổng Các Ước Trên Một Đoạn Đầu

Đề. Cho N≤1012N \le 10^{12}, tính σ(1)+σ(2)+⋯+σ(N)\sigma(1) + \sigma(2) + \dots + \sigma(N) modulo 109+710^9 + 7, với σ(x)\sigma(x) là tổng các ước của xx.

Lời giải. Đếm theo ước: ước dd có mặt trong đúng ⌊N/d⌋\lfloor N/d \rfloor số x≤Nx \le N, mỗi lần góp dd, nên tổng là 1⋅⌊N/1⌋+2⋅⌊N/2⌋+⋯+N⋅⌊N/N⌋1 \cdot \lfloor N/1 \rfloor + 2 \cdot \lfloor N/2 \rfloor + \dots + N \cdot \lfloor N/N \rfloor. Gộp khối: trong khối [l,r][l, r] có thương qq, phần góp là q⋅(l+⋯+r)q \cdot (l + \dots + r). Tổng l+⋯+r=(l+r)(r−l+1)2l + \dots + r = \frac{(l + r)(r - l + 1)}{2}; hai thừa số khác tính chẵn lẻ nên chia 2 trên thừa số chẵn trước, rồi mới lấy modulo, tránh tích 102410^{24} tràn. Khoảng 2⋅1062 \cdot 10^6 khối.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1e9 + 7;
ll n;

ll sum(ll l, ll r) {
    ll a = l + r, b = r - l + 1;
    if (a % 2 == 0) a /= 2;
    else b /= 2;
    return a % MOD * (b % MOD) % MOD;
}

signed main() {
    cin >> n;
    ll ans = 0;
    for (ll l = 1, r; l <= n; l = r + 1) {
        ll q = n / l;
        r = n / q;
        ans = (ans + q % MOD * sum(l, r)) % MOD;
    }
    cout << ans << '\n';
}
10

Khi đi thi

  • Đọc giới hạn trước khi chọn công cụ: một số N≤1012N \le 10^{12} thì thử chia tới căn (10610^6 phép), nhiều số cùng cỡ 10710^7 thì sàng, số mũ cỡ 101810^{18} hoặc có hai tầng lũy thừa thì lũy thừa nhanh và rút gọn số mũ theo p−1p - 1.
  • Viết long long ngay từ đầu, vòng lặp d * d <= n, chia trước nhân sau trong BCNN, và đưa phần dư âm về [0,m)[0, m); phần lớn lỗi ở bài số học là tràn số hoặc sai dấu, không phải sai ý.
  • Khi bài đếm theo ước hay bội, thử đảo cách đếm: mỗi dd chạy qua các bội của nó, hoặc gộp theo ⌊N/d⌋\lfloor N/d \rfloor; thấy điều kiện chính phương, chia hết, ƯCLN thì đổi sang điều kiện trên số mũ nguyên tố.

Tóm tắt

  • gcd⁡(a,b)=gcd⁡(b,a mod b)\gcd(a, b) = \gcd(b, a \bmod b) chạy O(log⁡)O(\log) bước, lcm⁡=agcd⁡⋅b\operatorname{lcm} = \frac{a}{\gcd} \cdot b với phép chia làm trước.
  • Hợp số nn có ước không quá n\sqrt n: kiểm tra nguyên tố, phân tích thừa số (nhớ thừa số cuối) và liệt kê ước theo cặp đều là O(n)O(\sqrt n); τ(n)=(e1+1)(e2+1)⋯\tau(n) = (e_1 + 1)(e_2 + 1) \cdots.
  • Sàng gạch bội từ p2p^2 với p≤Np \le \sqrt N trong O(Nlog⁡log⁡N)O(N \log\log N); spf\mathrm{spf} cho phân tích O(log⁡x)O(\log x), đếm theo bội cho τ\tau, σ\sigma, φ\varphi của mọi x≤Mx \le M.
  • ab mod ma^b \bmod m trong O(log⁡b)O(\log b); với pp nguyên tố, b−1=bp−2b^{-1} = b^{p-2} và số mũ rút gọn theo p−1p - 1 khi p∤ap \nmid a.
  • ⌊N/d⌋\lfloor N/d \rfloor có O(N)O(\sqrt N) giá trị nên gộp khối; vp(n!)=⌊n/p⌋+⌊n/p2⌋+⋯v_p(n!) = \lfloor n/p \rfloor + \lfloor n/p^2 \rfloor + \cdots.

Bài tập

1
Số Lớn Nhất Cùng Phần Dưcanuc80k0801Mọi số cần tìm có dạng qx+yqx + y; chọn qq lớn nhất sao cho qx+y≤nqx + y \le n.
LQDOJ
2
Ô Mang Tích Xcanuc80k0810Cố định hàng ii: cột còn lại là x/ix / i, hợp lệ khi i∣xi \mid x và x/i≤nx / i \le n.
LQDOJ
3
Danh Sách Ước Tăng Dầncanuc80k0803Duyệt dd tới N\sqrt N, mỗi ước sinh cặp (d,N/d)(d, N/d); để ý trường hợp hai số bằng nhau, rồi sắp xếp.
LQDOJ
4
Trừ Ước Nguyên Tố Nhỏ Nhấtcanuc80k0812Số chẵn luôn trừ 2; số lẻ trừ ước nguyên tố nhỏ nhất pp một lần rồi trở thành số chẵn.
LQDOJ
5
Ước Chung Trong Đoạncanuc80k0823Ước chung của hai số chính là ước của ƯCLN; liệt kê ước một lần, mỗi truy vấn chặt nhị phân (bài 05).
LQDOJ
6
Tổng Có Trọng Số Ướccanuc80k0817Đếm theo ước: ước dd góp d⋅(1+2+⋯+⌊N/d⌋)d \cdot (1 + 2 + \dots + \lfloor N/d \rfloor) vào tổng, không cần đếm ước của từng số.
LQDOJ
7
Số Thừa Số Trong Thương Giai Thừacanuc80k0831Sàng ước nguyên tố nhỏ nhất, Ω(x)=Ω(x/spf(x))+1\Omega(x) = \Omega(x / \mathrm{spf}(x)) + 1, rồi tổng tiền tố (bài 02).
LQDOJ
8
Ghép Cặp Thành Chính Phươngcanuc80k0845Mỗi số giữ lại các nguyên tố có số mũ lẻ; tích hai số là chính phương khi hai phần giữ lại bằng nhau, đếm theo nhóm.
LQDOJ
9
Liệt Kê Nguyên Tố Trên Các Đoạncanuc80k0833Sàng đoạn: gạch bội của các nguyên tố tới R\sqrt R từ max⁡(p2,⌈L/p⌉p)\max(p^2, \lceil L/p \rceil p); số 1 không nguyên tố.
LQDOJ
10
Sàng Và Truy Vấn Nguyên Tốcanuc80k0844Sàng tới 10810^8 cần mảng bit hoặc chỉ sàng số lẻ; mỗi truy vấn là một lần tra.
LQDOJ
11
Đếm Chuỗi Ngoặc Đúngcanuc80k0838Số chuỗi ngoặc đúng độ dài 2m2m là một số Catalan; tính tổ hợp theo modulo bằng giai thừa và nghịch đảo.
LQDOJ
12
Số Không Tận Cùng Theo Cơ Sốcanuc80k0827Phân tích bb ra nguyên tố; so số mũ của từng nguyên tố trong n!n! (công thức Legendre) với số mũ trong bb.
LQDOJ
13
Đếm Số Nguyên Tố Cùng Nhaucanuc80k0819Sàng hàm phi Euler tới 10610^6 một lần, mỗi test là một lần tra.
LQDOJ

Đọc thêm

  • Sàng nguyên tốVNOI WikiSàng Eratosthenes cùng các biến thể: chỉ sàng số lẻ, sàng tuyến tính, sàng đoạn.
  • Lũy thừa nhị phânVNOI WikiLũy thừa nhanh, rút gọn số mũ theo m−1m - 1 và φ(m)\varphi(m).
  • Euclidean algorithmcp-algorithmsChứng minh số bước bằng định lý Lamé và các cách tính ƯCLN, BCNN.
  • Modular Multiplicative Inversecp-algorithmsMọi cách tính nghịch đảo, kể cả bảng nghịch đảo 1..n1..n trong O(n)O(n).
  • 数论分块OI WikiVì sao ⌊n/i⌋\lfloor n/i \rfloor có O(n)O(\sqrt n) giá trị và cách gộp khối.