Skip to content

C17: Thuật toán STL & Số học

Tác giả: Hà Trí Kiên
Chủ đề: <numeric>, <algorithm> nâng cao, bitmask, số học thi đấu


1. Tổng quan

Bài này tổng hợp các thuật toán STL thường dùng trong thi đấu mà chưa được đề cập đầy đủ ở các bài trước.

flowchart TD
    A["Thuật toán STL"] --> B["numeric"]
    A --> C["algorithm nâng cao"]
    A --> D["Bitmask"]
    A --> E["Số học thi đấu"]

    B --> B1["accumulate"]
    B --> B2["partial_sum"]
    B --> B3["adjacent_difference"]
    B --> B4["iota"]
    B --> B5["gcd / lcm"]

    C --> C1["set_union / set_intersection"]
    C --> C2["merge / inplace_merge"]
    C --> C3["next_permutation"]

    D --> D1["__builtin_popcount"]
    D --> D2["__builtin_clz / ctz"]
    D --> D3["Bitmask enumeration"]

    E --> E1["Modular arithmetic"]
    E --> E2["Sieve of Eratosthenes"]
    E --> E3["Fast power"]

2. Thư viện <numeric>

2.1. accumulate — Tổng mảng

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> a = {1, 2, 3, 4, 5};

    // Tổng từ đầu đến cuối, giá trị khởi tạo = 0
    int sum = accumulate(a.begin(), a.end(), 0);
    cout << sum << endl;  // 15

    // Tích
    int product = accumulate(a.begin(), a.end(), 1, multiplies<int>());
    cout << product << endl;  // 120

    // Tổng với điều kiện (chỉ số chẵn)
    int sum_even = accumulate(a.begin(), a.end(), 0,
        [](int acc, int x) { return acc + (x % 2 == 0 ? x : 0); });
    cout << sum_even << endl;  // 6

    return 0;
}

Tránh tràn số

Khi tính tổng nhiều số lớn, dùng long long:

long long sum = accumulate(a.begin(), a.end(), 0LL);
//                                     khởi tạo ^^ 0LL

2.2. partial_sum — Tổng tiền tố (Prefix Sum)

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> a = {1, 2, 3, 4, 5};
    vector<int> pref(5);

    // pref[i] = a[0] + a[1] + ... + a[i]
    partial_sum(a.begin(), a.end(), pref.begin());
    // pref = {1, 3, 6, 10, 15}

    // Tính tổng đoạn [l, r] bằng prefix sum
    int l = 1, r = 3;
    int sumLR = pref[r] - (l > 0 ? pref[l - 1] : 0);
    cout << sumLR << endl;  // 2 + 3 + 4 = 9

    return 0;
}

2.3. adjacent_difference — Hiệu kề nhau

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> a = {1, 3, 6, 10, 15};
    vector<int> diff(5);

    // diff[0] = a[0], diff[i] = a[i] - a[i-1]
    adjacent_difference(a.begin(), a.end(), diff.begin());
    // diff = {1, 2, 3, 4, 5}

    // Hữu ích để kiểm tra dãy tăng đều
    return 0;
}

2.4. iota — Tạo dãy liên tiếp

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> a(10);

    // a = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    iota(a.begin(), a.end(), 0);

    // a = {5, 6, 7, 8, 9, 10, 11, 12, 13, 14}
    iota(a.begin(), a.end(), 5);

    // Hữu ích khi cần mảng chỉ số
    int n = 10;
    vector<int> idx(n);
    iota(idx.begin(), idx.end(), 0);
    sort(idx.begin(), idx.end(), [&](int i, int j) {
        return a[i] < a[j];  // Sắp xếp chỉ số theo giá trị
    });

    return 0;
}

2.5. gcd / lcm (C++17)

#include <bits/stdc++.h>
using namespace std;

int main() {
    // __gcd hoạt động trên mọi trình biên dịch (GCC extension)
    cout << __gcd(12, 18) << endl;  // 6

    // C++17: std::gcd, std::lcm (trong <numeric>)
    // cout << gcd(12, 18) << endl;  // Cần C++17
    // cout << lcm(4, 6) << endl;    // Cần C++17

    // GCD nhiều số
    vector<int> a = {12, 18, 24};
    int g = 0;
    for (int x : a) g = __gcd(g, x);
    cout << g << endl;  // 6

    return 0;
}

3. <algorithm> nâng cao

3.1. set_union / set_intersection / set_difference

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> a = {1, 2, 3, 4, 5};
    vector<int> b = {3, 4, 5, 6, 7};
    vector<int> res(10);

    // Hợp (union)
    auto it = set_union(a.begin(), a.end(), b.begin(), b.end(), res.begin());
    // res = {1, 2, 3, 4, 5, 6, 7, ...}

    // Giao (intersection)
    it = set_intersection(a.begin(), a.end(), b.begin(), b.end(), res.begin());
    // res = {3, 4, 5, ...}

    // Hiệu (difference) — phần tử trong a mà không trong b
    it = set_difference(a.begin(), a.end(), b.begin(), b.end(), res.begin());
    // res = {1, 2, ...}

    return 0;
}

Yêu cầu mảng đã sắp xếp

Hai mảng ab phải được sắp xếp tăng dần trước khi dùng các hàm này.

3.2. merge / inplace_merge

#include <bits/stdc++.h>
using namespace std;

int main() {
    // merge: trộn 2 mảng đã sắp xếp
    vector<int> a = {1, 3, 5};
    vector<int> b = {2, 4, 6};
    vector<int> res(6);
    merge(a.begin(), a.end(), b.begin(), b.end(), res.begin());
    // res = {1, 2, 3, 4, 5, 6}

    // inplace_merge: trộn 2 phần của cùng 1 mảng
    vector<int> c = {1, 3, 5, 2, 4, 6};
    inplace_merge(c.begin(), c.begin() + 3, c.end());
    // c = {1, 2, 3, 4, 5, 6}

    return 0;
}

3.3. next_permutation / prev_permutation

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> a = {1, 2, 3};

    // In tất cả hoán vị
    do {
        for (int x : a) cout << x << " ";
        cout << endl;
    } while (next_permutation(a.begin(), a.end()));
    // Output:
    // 1 2 3
    // 1 3 2
    // 2 1 3
    // 2 3 1
    // 3 1 2
    // 3 2 1

    // Lưu ý: mảng phải được sắp xếp tăng dần trước!

    return 0;
}

4. Bitmask

4.1. Các hàm built-in

#include <bits/stdc++.h>
using namespace std;

int main() {
    int x = 0b10110;  // = 22

    // __builtin_popcount: đếm số bit 1
    cout << __builtin_popcount(x) << endl;      // 3

    // __builtin_clz: đếm số bit 0 ở đầu (count leading zeros)
    cout << __builtin_clz(x) << endl;           // 27 (32-bit)

    // __builtin_ctz: đếm số bit 0 ở cuối (count trailing zeros)
    cout << __builtin_ctz(x) << endl;           // 1

    // __builtin_parity: parity (số bit 1 chẵn/lẻ)
    cout << __builtin_parity(x) << endl;        // 1 (3 bit 1 → lẻ)

    // Với long long
    long long y = (1LL << 50);
    cout << __builtin_popcountll(y) << endl;    // 1
    cout << __builtin_clzll(y) << endl;         // 13 (64-bit)

    return 0;
}

4.2. Bitmask enumeration — Duyệt tất cả tập con

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n = 4;

    // Duyệt tất cả tập con của {0, 1, 2, ..., n-1}
    for (int mask = 0; mask < (1 << n); mask++) {
        cout << "{ ";
        for (int i = 0; i < n; i++) {
            if (mask & (1 << i)) cout << i << " ";
        }
        cout << "}" << endl;
    }
    // Output:
    // { }
    // { 0 }
    // { 1 }
    // { 0 1 }
    // { 2 }
    // { 0 2 }
    // { 1 2 }
    // { 0 1 2 }
    // { 3 }
    // ...

    return 0;
}

4.3. Bitmask trong thi đấu

#include <bits/stdc++.h>
using namespace std;

int main() {
    // Bài toán: Cho n đồ vật, mỗi đồ vật có trọng lượng w[i].
    // Tìm tất cả cách chọn đồ vật sao cho tổng trọng lượng = S.

    int n = 4, S = 10;
    vector<int> w = {3, 5, 2, 7};

    for (int mask = 0; mask < (1 << n); mask++) {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            if (mask & (1 << i)) sum += w[i];
        }
        if (sum == S) {
            cout << "Found: { ";
            for (int i = 0; i < n; i++) {
                if (mask & (1 << i)) cout << w[i] << " ";
            }
            cout << "}" << endl;
        }
    }
    // Output: Found: { 3 5 2 }

    return 0;
}

4.4. Bit tricks thường dùng

// Kiểm tra bit thứ i
bool getBit(int x, int i) { return (x >> i) & 1; }

// Bật bit thứ i
int setBit(int x, int i) { return x | (1 << i); }

// Tắt bit thứ i
int clearBit(int x, int i) { return x & ~(1 << i); }

// Đảo bit thứ i
int toggleBit(int x, int i) { return x ^ (1 << i); }

// Lấy bit 1 cuối cùng (lowest set bit)
int lowBit(int x) { return x & (-x); }

// Tắt bit 1 cuối cùng
int clearLowest(int x) { return x & (x - 1); }

// Kiểm tra x có phải lũy thừa của 2
bool isPowerOf2(int x) { return x > 0 && (x & (x - 1)) == 0; }

5. Số học thi đấu

5.1. Modular arithmetic

const int MOD = 1e9 + 7;

// Cộng modulo
int add(int a, int b) { return (a + b) % MOD; }

// Trừ modulo
int sub(int a, int b) { return (a - b + MOD) % MOD; }

// Nhân modulo
int mul(int a, int b) { return (1LL * a * b) % MOD; }

// Lũy thừa nhanh modulo
int power(int base, int exp, int mod = MOD) {
    int result = 1;
    base %= mod;
    while (exp > 0) {
        if (exp & 1) result = (1LL * result * base) % mod;
        base = (1LL * base * base) % mod;
        exp >>= 1;
    }
    return result;
}

// Nghịch đảo modulo (Fermat's little theorem)
int inverse(int a, int mod = MOD) {
    return power(a, mod - 2, mod);
}

// Chia modulo
int div(int a, int b) { return mul(a, inverse(b)); }

5.2. Sieve of Eratosthenes

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e7 + 5;
bool isPrime[MAXN];
vector<int> primes;

void sieve() {
    fill(isPrime, isPrime + MAXN, true);
    isPrime[0] = isPrime[1] = false;

    for (int i = 2; i < MAXN; i++) {
        if (isPrime[i]) {
            primes.push_back(i);
            for (long long j = 1LL * i * i; j < MAXN; j += i) {
                isPrime[j] = false;
            }
        }
    }
}

int main() {
    sieve();

    // Kiểm tra số nguyên tố
    cout << isPrime[7] << endl;   // 1 (true)
    cout << isPrime[10] << endl;  // 0 (false)

    // In 100 số nguyên tố đầu tiên
    for (int i = 0; i < 100; i++) cout << primes[i] << " ";
    cout << endl;

    return 0;
}

5.3. Phân tích thừa số nguyên tố

#include <bits/stdc++.h>
using namespace std;

vector<pair<long long, int>> factorize(long long n) {
    vector<pair<long long, int>> factors;
    for (long long i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            int cnt = 0;
            while (n % i == 0) {
                n /= i;
                cnt++;
            }
            factors.push_back({i, cnt});
        }
    }
    if (n > 1) factors.push_back({n, 1});
    return factors;
}

int main() {
    long long n = 360;  // 360 = 2^3 * 3^2 * 5
    auto factors = factorize(n);

    for (auto [p, e] : factors) {
        cout << p << "^" << e << " ";
    }
    cout << endl;
    // Output: 2^3 3^2 5^1

    return 0;
}

5.4. Đếm ước số

#include <bits/stdc++.h>
using namespace std;

// Đếm số ước số của n
int countDivisors(long long n) {
    int cnt = 0;
    for (long long i = 1; i * i <= n; i++) {
        if (n % i == 0) {
            cnt++;
            if (i != n / i) cnt++;
        }
    }
    return cnt;
}

// Tổng ước số của n
long long sumDivisors(long long n) {
    long long sum = 0;
    for (long long i = 1; i * i <= n; i++) {
        if (n % i == 0) {
            sum += i;
            if (i != n / i) sum += n / i;
        }
    }
    return sum;
}

5.5. __int128 cho số rất lớn

#include <bits/stdc++.h>
using namespace std;

// __int128: 128-bit integer (GCC only)
// Phạm vi: ~10^38

void print128(__int128 x) {
    if (x == 0) { cout << 0; return; }
    if (x < 0) { cout << "-"; x = -x; }
    string s;
    while (x > 0) {
        s += (char)('0' + x % 10);
        x /= 10;
    }
    reverse(s.begin(), s.end());
    cout << s;
}

int main() {
    __int128 a = 1;
    for (int i = 1; i <= 40; i++) a *= i;

    cout << "40! = ";
    print128(a);
    cout << endl;
    // Output: 40! = 815915283247897734345611269596115894272000000000

    return 0;
}

6. Bảng tổng hợp hàm

Độ phức tạp trong bảng

  • \(O(n)\): Duyệt qua \(n\) phần tử — tuyến tính.
  • \(O(\log n)\): Chia đôi liên tục — rất nhanh, ngay cả với số lớn (\(10^{18}\) chỉ cần ~60 bước).
Hàm Thư viện Mô tả Độ phức tạp
accumulate <numeric> Tổng mảng \(O(n)\)
partial_sum <numeric> Prefix sum \(O(n)\)
adjacent_difference <numeric> Hiệu kề nhau \(O(n)\)
iota <numeric> Tạo dãy liên tiếp \(O(n)\)
gcd <numeric> (C++17) Ước chung lớn nhất \(O(\log n)\)
__gcd <algorithm> (GCC) Ước chung lớn nhất \(O(\log n)\)
lcm <numeric> Bội chung nhỏ nhất \(O(\log n)\)
set_union <algorithm> Hợp 2 tập \(O(n)\)
set_intersection <algorithm> Giao 2 tập \(O(n)\)
set_difference <algorithm> Hiệu 2 tập \(O(n)\)
merge <algorithm> Trộn 2 mảng \(O(n)\)
inplace_merge <algorithm> Trộn tại chỗ \(O(n)\)
next_permutation <algorithm> Hoán vị kế tiếp \(O(n)\)

Bài viết liên quan


Bài tiếp theo: C18: Xử lý xâu nâng cao →


💬 Bình luận