Khoá học C++ cơ bản
Bài học: Cấu Trúc Cây Phân Đoạn (Segment Tree) & Fenwick Tree (BIT)
🏆 +100 XP tiềm năng Thoát

Cấu Trúc Cây Phân Đoạn (Segment Tree) & Fenwick Tree (BIT)

2 khối nội dung 45 phút học tập MỞ BẢNG TRẮNG

Cấu Trúc Cây Phân Đoạn (Segment Tree) & Fenwick Tree (BIT)

Nội dung bài học

1. Bản chất bài toán truy vấn đoạn động (dynamic range queries)

Cho mảng $A$ gồm $N$ phần tử. Cần thực hiện liên tiếp $Q$ thao tác thuộc 2 loại:

  1. Cập nhật điểm (Point Update): Thay đổi giá trị $A[i] \gets v$ (hoặc $A[i] \gets A[i] + v$).
  2. Truy vấn đoạn (Range Query): Tính tổng $\sum_{k=L}^R A[k]$ hoặc tìm $\min_{k=L}^R A[k]$ / $\max_{k=L}^R A[k]$.
Cấu Trúc Khởi Tạo (Build) Cập Nhật Điểm (Update) Truy Vấn Đoạn (Query) Bộ Nhớ
Mảng Tiền Tố (Prefix Sum) $\mathcal{O}(N)$ $\mathcal{O}(N)$ (Quá chậm khi có update) $\mathcal{O}(1)$ $\mathcal{O}(N)$
Cây Fenwick (BIT) $\mathcal{O}(N)$ $\mathcal{O}(\log N)$ (Cực nhanh) $\mathcal{O}(\log N)$ $\mathcal{O}(N)$
Cây Phân Đoạn (Segment Tree) $\mathcal{O}(N)$ $\mathcal{O}(\log N)$ (Cực nhanh) $\mathcal{O}(\log N)$ $\mathcal{O}(4N)$

So sánh các cấu trúc Range Query

2. Cây fenwick (Binary Indexed Tree — BIT)

Cây Fenwick và Phép toán Lowbit

2.1. Phép toán ma thuật: lowbit(x) = x & (-x)

Phép toán x & (-x) trích xuất bit $1$ thấp nhất (trọng số nhỏ nhất) của số nguyên $x$.

  • Mỗi vị trí $x$ trong mảng bit[x] quản lý tổng của một đoạn con có độ dài đúng bằng lowbit(x) kết thúc tại $x$: $$\text{Đoạn quản lý của } x = (x - \text{lowbit}(x), x]$$

2.2. Hai thao tác cốt lõi siêu tinh gọn (chỉ 5 dòng code)

void update(int x, long long val) {
    for (; x <= n; x += x & -x) bit[x] += val;
}

long long query(int x) { // Tính tổng tiền tố A[1..x]
    long long sum = 0;
    for (; x > 0; x -= x & -x) sum += bit[x];

    return sum;
}

long long range_query(int L, int R) {
    return query(R) - query(L - 1);
}

3. Cây phân đoạn (Segment Tree)

Cây Phân Đoạn Segment Tree

3.1. Cấu trúc cây nhị phân hoàn hảo

  • Cây phân đoạn biểu diễn mảng quản lý theo cây nhị phân: Nút gốc $id = 1$ quản lý toàn đoạn $[1, N]$.
  • Nút con trái quản lý nửa trái $[L, mid]$ tại vị trí $2 \cdot id$.
  • Nút con phải quản lý nửa phải $[mid + 1, R]$ tại vị trí $2 \cdot id + 1$.
  • Quy tắc bộ nhớ: Mảng cây Segment Tree cần khai báo $4N$ phần tử để đảm bảo không bị tràn chỉ số khi $N$ không phải là lũy thừa của 2.

3.2. Ưu thế vượt trội của Segment Tree

Khác với Fenwick Tree chủ yếu hỗ trợ phép toán có tính nghịch đảo (như phép cộng tổng), Segment Tree hỗ trợ MỌI PHÉP TOÁN KẾT HỢP (Associative Operations):

  • Tìm giá trị nhỏ nhất / lớn nhất trên đoạn (Range Minimum / Maximum Query — RMQ).
  • Tìm ước chung lớn nhất trên đoạn ($\text{GCD}(A[L \dots R])$).
  • Đếm số lượng phần tử đạt cực đại trên đoạn.

4. Các bẫy lỗi lập trình kinh điển (bug traps)

  1. Bẫy quên khai báo mảng Segment Tree kích thước $4N$: * Khai báo tree[2 N] hoặc tree[N] sẽ bị tràn mảng (Out of Bounds) khi $N = 10^5$. Bắt buộc phải khai báo kích thước tối thiểu $4N$ (vector<long long> tree(4 n + 5)).

  2. Bẫy chỉ số 0-based của Fenwick Tree (Vòng lặp vô tận): * Trong Fenwick Tree, lowbit(0) = 0 & -0 = 0. Nếu gọi update(0, val) hoặc query(0), vòng lặp $x \gets x + (x \ \& \ -x)$ sẽ biến thành x += 0 và chạy vô tận $\implies$ Time Limit Exceeded! * Bất biến sống còn: Fenwick Tree BẮT BUỘC DÙNG CHỈ SỐ 1-BASED ($x \ge 1$).

  3. Bẫy tràn số khi cộng dồn tổng trên cây: * Mảng $N = 10^5$ phần tử có giá trị $10^9 \implies$ Tổng đoạn có thể lên tới $10^{14}$. Mảng treebit bắt buộc phải dùng kiểu long long.

5. Mẫu cài đặt chuẩn thi đấu (competitive templates)

Mẫu 1: Cây fenwick (BIT) point update & range sum query

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

struct FenwickTree {
    int n;
    vector<long long> bit;

    FenwickTree(int n) : n(n), bit(n + 1, 0) {}

    void update(int x, long long val) {
        for (; x <= n; x += x & -x) {
            bit[x] += val;
        }
    }

    long long query(int x) {
        long long sum = 0;
        for (; x > 0; x -= x & -x) {

            sum += bit[x];
        }
        return sum;
    }

    long long queryRange(int l, int r) {
        if (l > r) return 0;

        return query(r) - query(l - 1);
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, q;
    if (!(cin >> n >> q)) return 0;

    if (n <= 0) return 0;

    FenwickTree ft(n);

    for (int i = 1; i <= n; ++i) {
        long long x;
        cin >> x;

        ft.update(i, x);
    }

    while (q--) {
        int type;
        cin >> type;

        if (type == 1) { // Update: A[pos] += val
            int pos;
            long long val;
            cin >> pos >> val;

            ft.update(pos, val);
        } else { // Query: Sum(L..R)
            int l, r;
            cin >> l >> r;

            cout << ft.queryRange(l, r) << "\n";
        }
    }

    return 0;
}

Mẫu 2: Cây phân đoạn (Segment Tree) range minimum query (RMQ)

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

const long long INF = 1e18;

struct SegmentTree {
    int n;
    vector<long long> tree;

    SegmentTree(int n) : n(n), tree(4 * n + 5, INF) {}

    void build(const vector<long long>& a, int id, int l, int r) {
        if (l == r) {
            tree[id] = a[l];
            return;
        }
        int mid = (l + r) / 2;
        build(a, 2 * id, l, mid);
        build(a, 2 * id + 1, mid + 1, r);
        tree[id] = min(tree[2 * id], tree[2 * id + 1]);
    }

    void update(int id, int l, int r, int pos, long long val) {
        if (l == r) {
            tree[id] = val;
            return;
        }
        int mid = (l + r) / 2;
        if (pos <= mid) {
            update(2 * id, l, mid, pos, val);
        } else {
            update(2 * id + 1, mid + 1, r, pos, val);
        }
        tree[id] = min(tree[2 * id], tree[2 * id + 1]);
    }

    long long query(int id, int l, int r, int u, int v) {
        if (v < l || r < u) return INF; // Ngoài đoạn
        if (u <= l && r <= v) return tree[id]; // Nằm trọn trong đoạn

        int mid = (l + r) / 2;
        return min(query(2 * id, l, mid, u, v),
                   query(2 * id + 1, mid + 1, r, u, v));
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, q;
    if (!(cin >> n >> q)) return 0;

    if (n <= 0) return 0;

    vector<long long> a(n + 1);

    for (int i = 1; i <= n; ++i) {
        cin >> a[i];

    }

    SegmentTree st(n);
    st.build(a, 1, 1, n);

    while (q--) {
        int type;
        cin >> type;

        if (type == 1) { // Gán A[pos] = val
            int pos;
            long long val;
            cin >> pos >> val;

            st.update(1, 1, n, pos, val);
        } else { // Tìm Min trong đoạn [L, R]
            int l, r;
            cin >> l >> r;

            cout << st.query(1, 1, n, l, r) << "\n";
        }
    }

    return 0;
}
1
A
B
C
D
2
A
B
C
D
3
A
B
C
D
4
A
B
C
D
5
A
B
C
D
6
A
B
C
D
7
A
B
C
D
8
A
B
C
D
9
A
B
C
D
10
A
B
C
D
11
A
B
C
D
12
A
B
C
D
13
A
B
C
D
14
A
B
C
D
15
A
B
C
D

Bài Tập Thực Hành & Rèn Luyện

15 Bài tập thực hành

Nhấn vào nút "Làm bài" bên dưới để mở giao diện làm bài trực tuyến (Online Judge) và thực hiện viết mã nguồn cho bài tập này.

Cài Đặt Fenwick Tree Tính Tổng Đoạn (Range Sum)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Cài Đặt Segment Tree Tìm Min Đoạn (RMQ)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Cập Nhật Đoạn & Truy Vấn Điểm (Range Update Point Query)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Tìm Max Đoạn & Đếm Số Lần Xuất Hiện
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Đếm Số Cặp Nghịch Thế (Inversion Count)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Truy Vấn Ước Chung Lớn Nhất Đoạn (Range GCD)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Tìm Phần Tử Thứ K Nhỏ Nhất (K-th Element on BIT)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Tìm Vị Trí Đầu Tiên Có Giá Trị Lớn Hơn Hoặc Bằng X
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Dãy Con Tăng Dài Nhất LIS Bằng Fenwick Tree
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Đoạn Con Có Tổng Lớn Nhất Trên Đoạn (Maximum Subarray Query)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Đếm Số Điểm Trong Hình Chữ Nhật (2D Range Query)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Cập Nhật Phân Đoạn Nâng Cao
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Cây Fenwick 2D Tính Tổng Hình Chữ Nhật (2D BIT)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Lazy Propagation — Cập Nhật & Truy Vấn Đoạn (Range Add Range Sum)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài
Hệ Thống Quản Lý Dữ Liệu Olympic (Range Master)
Độ khó: mediumĐiểm tối đa: 100.0 điểm
Làm bài