Array Preprocessing

➕ Prefix Sum

Prefix Sum là kỹ thuật tiền xử lý nền tảng: biến truy vấn tổng đoạn từ O(n) xuống O(1), mở rộng sang đếm tần suất, tổng 2D, prefix modulo và nhiều bài toán đoạn con.

BuildO(n)
Range SumO(1)
MemoryO(n)
Static ArrayPhù hợp nhiều truy vấn

🎯 Mục tiêu bài học

Cần hiểu

  • Ý nghĩa của prefix[i].
  • Hai cách đánh chỉ số phổ biến.
  • Công thức tổng đoạn [L,R].
  • Vì sao phải trừ phần bị tính thừa.
  • Cách mở rộng sang ma trận.

Khi nào dùng?

  • Mảng không thay đổi.
  • Nhiều truy vấn tổng đoạn.
  • Đếm số phần tử thỏa điều kiện trong đoạn.
  • Tổng hình chữ nhật 2D.
  • Các bài prefix modulo, prefix count.

1. Đặt vấn đề

Bài toán mở đầu.
Cho mảng a[0..n-1]q truy vấn. Mỗi truy vấn cho L,R, yêu cầu tính tổng a[L]+...+a[R].

1.1. Cách làm trực tiếp

Mỗi truy vấn duyệt từ L đến R và cộng từng phần tử.

Cách làmTiền xử lýMỗi truy vấnTổng
Duyệt trực tiếpO(1)O(R-L+1)Có thể O(nq)
Prefix SumO(n)O(1)O(n+q)

1.2. Quan sát quan trọng

Nếu biết tổng từ đầu đến R và tổng từ đầu đến L−1, ta lấy hiệu:

sum(L,R) = prefix[R] - prefix[L-1]
Kết luận: trả giá O(n) một lần để mọi truy vấn sau đó chỉ còn O(1).

2. Ý tưởng và định nghĩa

Cách 1: prefix kích thước n

prefix[i] là tổng đoạn [0,i].

prefix[0]=a[0]
prefix[i]=prefix[i-1]+a[i]

Phải xử lý riêng L=0.

Cách 2: prefix kích thước n+1

prefix[i] là tổng của i phần tử đầu.

prefix[0]=0
prefix[i+1]=prefix[i]+a[i]
sum(L,R)=prefix[R+1]-prefix[L]
Khuyến nghị: dùng prefix kích thước n+1 để tránh nhánh riêng cho L=0.

3. Mô phỏng xây Prefix Sum

Mảng a

prefix

Nhấn “Bước tiếp” để xây mảng prefix.

4. Mô phỏng truy vấn tổng đoạn

sum(L,R)=prefix[R+1]-prefix[L]

5. Prefix Sum 2D

Dùng để tính tổng hình chữ nhật trên ma trận trong O(1).

pref[i][j] = tổng hình chữ nhật từ (0,0) đến (i-1,j-1)
rect = pref[x2+1][y2+1] - pref[x1][y2+1] - pref[x2+1][y1] + pref[x1][y1]

Ma trận mẫu 4×5

Bao hàm–loại trừ: vùng giao bị trừ hai lần nên phải cộng lại một lần.

6. Phân tích thuật toán

Thành phầnThời gianBộ nhớGhi chú
Build 1DO(n)O(n)Mỗi phần tử xử lý một lần.
Query đoạnO(1)Không thêmDùng hiệu hai prefix.
Build 2DO(nm)O(nm)Mỗi ô xử lý một lần.
Query hình chữ nhậtO(1)Không thêmDùng bốn giá trị prefix.
UpdateKhông phù hợpNên dùng Fenwick/Segment Tree.

7. Code mẫu

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

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

    int n, q;
    cin >> n >> q;

    vector<long long> a(n);
    for (long long &x : a) cin >> x;

    vector<long long> prefix(n + 1, 0);
    for (int i = 0; i < n; i++) {
        prefix[i + 1] = prefix[i] + a[i];
    }

    while (q--) {
        int l, r;
        cin >> l >> r;
        --l; --r;
        cout << prefix[r + 1] - prefix[l] << '\n';
    }
}
vector<vector<long long>> pref(n + 1, vector<long long>(m + 1, 0));

for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
        pref[i + 1][j + 1]
            = a[i][j]
            + pref[i][j + 1]
            + pref[i + 1][j]
            - pref[i][j];
    }
}

long long rectSum(int x1, int y1, int x2, int y2) {
    return pref[x2 + 1][y2 + 1]
         - pref[x1][y2 + 1]
         - pref[x2 + 1][y1]
         + pref[x1][y1];
}
def build_prefix(a):
    prefix = [0] * (len(a) + 1)
    for i, value in enumerate(a):
        prefix[i + 1] = prefix[i] + value
    return prefix


def range_sum(prefix, left, right):
    return prefix[right + 1] - prefix[left]

8. Giải thích code

8.1. Vì sao prefix có kích thước n+1?

prefix[0]=0 là tổng của 0 phần tử, giúp mọi truy vấn dùng chung một công thức.

8.2. Vì sao ghi vào prefix[i+1]?

prefix[i+1] là tổng của i+1 phần tử đầu, tức đoạn a[0..i].

8.3. Vì sao query là prefix[R+1]-prefix[L]?

Tổng từ 0 đến R trừ tổng từ 0 đến L−1 sẽ còn đúng đoạn L đến R.

8.4. Vì sao cần long long?

Tổng nhiều số int có thể vượt 32-bit, nên nên dùng long long.

8.5. Prefix Sum có update tốt không?

Không. Một thay đổi ở a[i] làm thay đổi toàn bộ prefix phía sau. Khi có nhiều update, dùng Fenwick Tree hoặc Segment Tree.

9. Lỗi thường gặp

  • Nhầm giữa prefix kích thước n và n+1.
  • Trừ sai: dùng prefix[R]-prefix[L].
  • Quên đổi chỉ số 1-based sang 0-based.
  • Dùng int cho tổng lớn.
  • Quên cộng lại phần giao trong 2D.
  • Nhầm đoạn đóng [L,R] với đoạn nửa mở [L,R).
  • Dùng Prefix Sum khi có nhiều update.

10. Quiz và bài tập

Câu 1. Với prefix kích thước n+1, tổng [L,R] là gì?
Câu 2. Query tổng đoạn sau tiền xử lý có độ phức tạp?
Câu 3. Nếu có nhiều update điểm, nên dùng gì?

📘 Cơ bản

  1. Tính tổng q đoạn.
  2. Đếm số chẵn trong đoạn bằng prefix count.
  3. Tính trung bình đoạn.

📗 Trung bình

  1. Đếm đoạn con có tổng K.
  2. Đoạn dài cố định có tổng lớn nhất.
  3. Prefix Sum 2D.

📙 Nâng cao

  1. Prefix + hash map.
  2. Prefix modulo.
  3. Prefix xor.

🐉 HSG

  1. Difference array + Prefix Sum.
  2. Maximum subarray có ràng buộc.
  3. Prefix Sum + coordinate compression.