Kỹ Thuật Mảng: Hai Con Trỏ, Cửa Sổ Trượt, Mảng Tiền Tố & Mảng Hiệu
1. Khái niệm & bản chất của tối ưu hóa tuyến tính trên mảng
Trong lập trình thi đấu, các kỹ thuật xử lý mảng như Hai con trỏ (Two Pointers), Cửa sổ trượt (Sliding Window), Mảng tiền tố (Prefix Sum), Mảng hiệu (Difference Array) và Nén tọa độ (Coordinate Compression) là bộ công cụ nền tảng giúp chuyển đổi các thuật toán ngây thơ đa biến $\mathcal{O}(N^2)$ hoặc $\mathcal{O}(N \times Q)$ về độ phức tạp tối ưu tuyến tính $\mathcal{O}(N)$ hoặc $\mathcal{O}(N \log N)$.
Ở Level 2, ta tập trung vào Kỹ thuật kết hợp đa chiều & Mảng 2D:
- Hai con trỏ co giãn & Cửa sổ trượt linh hoạt: Duy trì bất biến về tần suất, số lượng phần tử phân biệt hoặc tổng điều kiện khi kích thước cửa sổ thay đổi liên tục.
- Mảng tiền tố 2D (2D Prefix Sum): Trả lời truy vấn tính tổng hình chữ nhật con bất kỳ trên ma trận $N \times M$ trong $\mathcal{O}(1)$.
- Mảng hiệu 2D (2D Difference Array): Cập nhật cộng một giá trị lên toàn bộ vùng hình chữ nhật trong $\mathcal{O}(1)$ và khôi phục ma trận trong $\mathcal{O}(NM)$.
- Nén tọa độ (Coordinate Compression): Ánh xạ các giá trị rời rạc rất lớn ($A_i \le 10^9$) về dải chỉ số nhỏ liên tiếp $[1, N]$ mà vẫn bảo toàn hoàn toàn quan hệ thứ tự $A_i < A_j$.
2. Mảng tiền tố 2D
2.1. Công thức Mảng tiền tố 2D (2D Prefix Sum)
Định nghĩa: $pref[i][j]$ là tổng các phần tử trong hình chữ nhật từ góc trên-trái $(1, 1)$ đến $(i, j)$: $$pref[i][j] = pref[i-1][j] + pref[i][j-1] - pref[i-1][j-1] + A[i][j]$$
Truy vấn tổng hình chữ nhật từ $(x_1, y_1)$ đến $(x_2, y_2)$ trong $\mathcal{O}(1)$: $$\text{Sum}(x_1, y_1, x_2, y_2) = pref[x_2][y_2] - pref[x_1-1][y_2] - pref[x_2][y_1-1] + pref[x_1-1][y_1-1]$$
2.2. Công thức Mảng hiệu 2D (2D Difference Array)
Để cộng thêm giá trị $V$ vào toàn bộ hình chữ nhật $[x_1, y_1] \to [x_2, y_2]$ trong $\mathcal{O}(1)$:
- $diff[x_1][y_1] \mathrel{+}= V$
- $diff[x_1][y_2 + 1] \mathrel{-}= V$
- $diff[x_2 + 1][y_1] \mathrel{-}= V$
- $diff[x_2 + 1][y_2 + 1] \mathrel{+}= V$
Sau khi thực hiện tất cả các cập nhật, chạy công thức Prefix Sum 2D trên mảng $diff$ để thu lại giá trị thực tế của ma trận.
3. Kỹ thuật nén tọa độ (Coordinate Compression)
3.1. Động lực & Cơ chế thực thi
Khi một bài toán có các giá trị tọa độ $X_i \in [-10^9, 10^9]$ nhưng số lượng điểm $N \le 10^5$, ta không thể dùng mảng đánh dấu kích thước $10^9$.
Ta nén các giá trị này về tập $\{0, 1, \dots, K-1\}$ với $K \le N$:
vector<int> vals = a;
sort(vals.begin(), vals.end());
vals.erase(unique(vals.begin(), vals.end()), vals.end());
// Tìm chỉ số đã nén (0-based) của a[i] trong O(log N)
for (int i = 0; i < n; ++i) {
int compressed_val = lower_bound(vals.begin(), vals.end(), a[i]) - vals.begin();
}
4. Kỹ thuật hai con trỏ co giãn (Dynamic Sliding Window)
4.1. Bài toán: Tìm đoạn con ngắn nhất có tổng $\ge S$
Với mảng gồm các số nguyên dương $A_i > 0$:
- Khi mở rộng con trỏ phải $R$, tổng
current_sumtăng ngặt. - Khi
current_sum >= S, ta thu hẹp con trỏ trái $L$ để tìm độ dài ngắn nhất thỏa mãn.
int min_len = n + 1;
long long current_sum = 0;
int l = 0;
for (int r = 0; r < n; ++r) {
current_sum += a[r];
while (current_sum >= s) {
min_len = min(min_len, r - l + 1);
current_sum -= a[l];
l++;
}
}
5. Mẫu cài đặt chuẩn thi đấu: 2D Prefix Sum
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, q;
if (!(cin >> n >> m >> q)) return 0;
vector<vector<long long>> a(n + 1, vector<long long>(m + 1, 0));
vector<vector<long long>> pref(n + 1, vector<long long>(m + 1, 0));
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> a[i][j];
pref[i][j] = pref[i - 1][j] + pref[i][j - 1] - pref[i - 1][j - 1] + a[i][j];
}
}
while (q--) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
long long ans = pref[x2][y2] - pref[x1 - 1][y2] - pref[x2][y1 - 1] + pref[x1 - 1][y1 - 1];
cout << ans << "\n";
}
return 0;
}
6. Ranh giới áp dụng
| Kỹ Thuật | Phạm Vi Sử Dụng | Độ Phức Tạp |
|---|---|---|
| Two Pointers / Sliding Window | Mảng 1D đơn điệu, tìm đoạn con thỏa mãn tính chất | $\mathcal{O}(N)$ |
| 2D Prefix Sum | Truy vấn tổng ma trận con tĩnh | Tiền xử lý $\mathcal{O}(NM)$, truy vấn $\mathcal{O}(1)$ |
| 2D Difference Array | Cập nhật cộng hình chữ nhật hàng loạt rồi mới truy vấn | Cập nhật $\mathcal{O}(1)$, khôi phục $\mathcal{O}(NM)$ |
| Coordinate Compression | Tọa độ lớn $10^9$ cần đưa về dải nhỏ để làm mảng đếm/cây | $\mathcal{O}(N \log N)$ |