Xử Lý Chuỗi Ký Tự, String Hashing & Số Nguyên Lớn
1. Khái niệm & cấu trúc 3 phần của Chuyên đề 15
Chuyên đề 15 là bài học tổng hợp cuối cùng của khóa học Level 2, tích hợp 3 mảng kiến thức lớn:
- 15.1. Xử lý xâu cơ bản & Palindrome: Các thao tác chuẩn trên
string, đếm tần suất ký tự, kỹ thuật mở rộng tâm (Expand Around Center) tìm xâu con đối xứng dài nhất trong $\mathcal{O}(N^2)$. - 15.2. Kỹ thuật Băm chuỗi đa thức (Rolling Hash / Polynomial Hashing): Biến đổi một xâu ký tự thành một số nguyên duy nhất theo modulo, cho phép so sánh hai xâu con bất kỳ $S[L \dots R]$ trong thời gian $\mathcal{O}(1)$ (thay vì $\mathcal{O}(N)$).
- 15.3. Xử lý số nguyên lớn (Big Integer): Tự xây dựng cấu trúc số nguyên lớn để thực hiện các phép cộng, trừ, nhân hai số có hàng nghìn chữ số.
2. Kỹ thuật Băm chuỗi đa thức
2.1. Công thức hàm băm tiền tố
Cho xâu $S$ độ dài $N$, cơ số $Base = 311$ (hoặc $31$) và modulo $MOD = 10^9 + 7$:
- Mảng băm tiền tố $hash[i] = (S[0] \cdot Base^i + S[1] \cdot Base^{i-1} + \dots + S[i]) \bmod MOD$.
- Công thức tính mã băm của đoạn con $S[L \dots R]$ (1-based) trong $\mathcal{O}(1)$: $$\text{get\_hash}(L, R) = (hash[R] - hash[L - 1] \times Base^{R - L + 1} \bmod MOD + MOD) \bmod MOD$$
2.2. Kỹ thuật Băm kép (Double Hash) chống đụng độ $100\%$
Để tránh việc hai xâu khác nhau có cùng mã băm (Hash Collision) do nguyên lý Dirichlet khi số lượng truy vấn lớn ($Q = 10^5$), ta sử dụng đồng thời hai cặp $(Base_1, MOD_1)$ và $(Base_2, MOD_2)$ khác nhau (ví dụ: $MOD_1 = 10^9+7, MOD_2 = 10^9+9$). Mã băm lúc này là một cặp số pair<long long, long long>. Khả năng đụng độ giảm xuống $\frac{1}{MOD_1 \times MOD_2} \approx 10^{-18}$ (gần như bằng 0 tuyệt đối).
const long long BASE = 311;
const long long MOD = 1000000007;
long long h[1000005];
long long pw[1000005];
void init_hash(const string &s) {
int n = s.size();
pw[0] = 1;
for (int i = 1; i <= n; ++i) pw[i] = (pw[i - 1] * BASE) % MOD;
h[0] = 0;
for (int i = 0; i < n; ++i) {
h[i + 1] = (h[i] * BASE + s[i]) % MOD;
}
}
long long get_hash(int l, int r) { // 1-based indexing
long long res = (h[r] - h[l - 1] * pw[r - l + 1]) % MOD;
return (res + MOD) % MOD;
}
3. Cấu trúc số nguyên lớn (Big Integer Addition, Subtraction, Multiplication)
3.1. Phép cộng hai số nguyên lớn
string add_bigint(string a, string b) {
while (a.size() < b.size()) a = "0" + a;
while (b.size() < a.size()) b = "0" + b;
int carry = 0;
string res = "";
for (int i = (int)a.size() - 1; i >= 0; --i) {
int sum = (a[i] - '0') + (b[i] - '0') + carry;
carry = sum / 10;
res += to_string(sum % 10);
}
if (carry) res += to_string(carry);
reverse(res.begin(), res.end());
return res;
}
3.2. Phép nhân hai số nguyên lớn
string multiply_bigint(string a, string b) {
int n = a.size(), m = b.size();
vector<int> res(n + m, 0);
for (int i = n - 1; i >= 0; --i) {
for (int j = m - 1; j >= 0; --j) {
int mul = (a[i] - '0') * (b[j] - '0');
int p1 = i + j, p2 = i + j + 1;
int sum = mul + res[p2];
res[p2] = sum % 10;
res[p1] += sum / 10;
}
}
string s = "";
for (int val : res) {
if (!(s.empty() && val == 0)) s += to_string(val);
}
return s.empty() ? "0" : s;
}
4. Ranh giới áp dụng
| Tình Huống Bài Toán | Kỹ Thuật Tối Ưu | Độ Phức Tạp |
|---|---|---|
| So sánh nhiều xâu con, tìm xâu con chung, đếm xâu đối xứng | String Hashing (Rolling Hash) | Tiền xử lý $\mathcal{O}(N)$, truy vấn $\mathcal{O}(1)$ |
| Phép tính số học với số có độ dài đến $10^4$ chữ số | Big Integer | Cộng $\mathcal{O}(N)$, Nhân $\mathcal{O}(NM)$ |
| Khớp mẫu xâu cơ bản | KMP hoặc String Hashing | $\mathcal{O}(N + M)$ |