Hash xâu
So sánh hai xâu dài L mất L bước. Một bài có 105 câu hỏi “đoạn từ i đến j có giống đoạn từ k đến l không?” thì có thể mất tới 1010 bước. Hash xâu biến mỗi đoạn thành một số, rồi so sánh hai số thay vì so sánh từng ký tự. Nhờ tổng tiền tố, hash của mọi đoạn tính được trong một phép tính.
Hash đa thức
Phần tiêu đề “Hash đa thức”Đổi mỗi ký tự thành một số (ví dụ a → 1, b → 2, …). Hash của xâu c1c2…cL
là giá trị của một đa thức tại cơ số B, lấy dư cho một số nguyên tố
lớn M:
hash = (c1 × BL - 1 + c2 × BL - 2 + … + cL) mod M
Giống như đọc xâu như một số viết trong hệ cơ số B. Hai xâu giống nhau luôn có hash bằng nhau. Hai xâu khác nhau rất hiếm khi có hash bằng nhau (gọi là trùng hash), với M khoảng 109 xác suất cỡ 1 / M cho mỗi lần so sánh.
Hash tiền tố và hash của một đoạn
Phần tiêu đề “Hash tiền tố và hash của một đoạn”Tính H[i] là hash của i ký tự đầu: H[0] = 0, H[i] = (H[i - 1] × B + ci) mod M. Khi đó hash của đoạn từ vị trí l + 1 đến r (đánh số từ 1) là:
hash(l + 1, r) = (H[r] - H[l] × Br - l) mod M
vì H[r] chứa cả phần đầu H[l] đã bị “đẩy lên” r - l bậc. Tính sẵn lũy thừa P[k] = Bk mod M.
Xâu abab với B = 10 (không lấy dư cho dễ thấy, a = 1, b = 2): H = 0, 1, 12, 121, 1212. Đoạn từ 3 đến 4 là
H[4] - H[2] × 102 = 1212 - 1200 = 12, đúng bằng hash của ab (đoạn từ 1 đến 2 cũng là H[2] = 12).
import sysdu_lieu = sys.stdin.read().split()s, q = du_lieu[0], int(du_lieu[1])n = len(s)B, M = 131, (1 << 61) - 1 # M nguyên tố rất lớn, Python tính số lớn đượcH = [0] * (n + 1)P = [1] * (n + 1)for i, c in enumerate(s): H[i + 1] = (H[i] * B + ord(c)) % M P[i + 1] = P[i] * B % M
def hash_doan(l, r): # đoạn s[l:r], đánh số từ 0, không gồm r return (H[r] - H[l] * P[r - l]) % M
ket_qua = []for t in range(q): a, b, c, d = (int(x) for x in du_lieu[2 + 4 * t:6 + 4 * t]) # hai đoạn [a, b] và [c, d], đánh số từ 1 giong = b - a == d - c and hash_doan(a - 1, b) == hash_doan(c - 1, d) ket_qua.append("YES" if giong else "NO")print("\n".join(ket_qua))#include <iostream>#include <string>#include <vector>using namespace std;
typedef unsigned long long ull;const ull M = (1ULL << 61) - 1, B = 131;
ull nhan(ull a, ull b) { // (a * b) mod M không tràn số return (unsigned __int128)a * b % M;}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); string s; int q; cin >> s >> q; int n = s.size(); vector<ull> H(n + 1, 0), P(n + 1, 1); for (int i = 0; i < n; i++) { H[i + 1] = (nhan(H[i], B) + s[i]) % M; P[i + 1] = nhan(P[i], B); } auto hashDoan = [&](int l, int r) { // đoạn [l, r), đánh số từ 0 return (H[r] + M - nhan(H[l], P[r - l])) % M; }; while (q--) { int a, b, c, d; cin >> a >> b >> c >> d; bool giong = b - a == d - c && hashDoan(a - 1, b) == hashDoan(c - 1, d); cout << (giong ? "YES" : "NO") << "\n"; } return 0;}Chọn cơ số và modulo
Phần tiêu đề “Chọn cơ số và modulo”- Cơ số B lớn hơn số loại ký tự (với chữ cái và chữ số, 131 là đủ), modulo M là số nguyên tố lớn.
- Với M = 109 + 7, mỗi lần so sánh có khả năng trùng khoảng 10-9; nhưng nếu so sánh rất nhiều cặp (ví dụ 1010 cặp xâu con) thì gần như chắc chắn có trùng. Khi đó dùng M = 261 - 1 (như chương trình trên) hoặc hai modulo khác nhau cùng lúc.
- Hash chỉ cho biết “gần như chắc chắn bằng nhau”. Hash khác nhau thì hai xâu chắc chắn khác nhau.
Luyện tập
Phần tiêu đề “Luyện tập”Gồm 14 bài, chia thành 4 nhóm từ dễ đến khó. Mỗi bài có một dữ liệu kiểm tra: các bạn chạy chương trình của mình với dữ liệu đó rồi so với đáp số.
Nhóm 1. So sánh đoạn và tìm mẫu
Phần tiêu đề “Nhóm 1. So sánh đoạn và tìm mẫu”Bài 1. Hai đoạn có giống nhau không
Phần tiêu đề “Bài 1. Hai đoạn có giống nhau không”Dòng đầu là xâu s (chữ cái thường, 1 ≤ |s| ≤ 2 × 105) và số q (1 ≤ q ≤ 2 × 105), q dòng sau mỗi dòng có a, b, c, d. Với mỗi câu hỏi, in ra YES nếu đoạn sa…sb giống hệt đoạn sc…sd (đánh số từ 1), ngược lại NO.
Ví dụ:
| Nhập | In ra |
|---|---|
abacaba 31 3 5 71 2 3 42 2 6 6 | YESNOYES |
Kiểm tra: nhập
bumbiibumbii 41 6 7 121 3 7 94 5 10 112 3 5 6Bài 2. Đếm số lần xuất hiện của mẫu
Phần tiêu đề “Bài 2. Đếm số lần xuất hiện của mẫu”Dòng đầu là xâu t, dòng thứ hai là mẫu p (chữ cái thường, 1 ≤ |p| ≤ |t| ≤ 106). In ra số vị trí trong t mà p xuất hiện bắt đầu từ đó (các lần xuất hiện có thể chồng lên nhau).
Ví dụ:
| Nhập | In ra |
|---|---|
abababaaba | 3 |
Kiểm tra: nhập
bumbiibumbiibumbiibiiBài 3. Đoạn đối xứng
Phần tiêu đề “Bài 3. Đoạn đối xứng”Dòng đầu là xâu s (chữ cái thường, 1 ≤ |s| ≤ 2 × 105) và số q (1 ≤ q ≤ 2 × 105), q dòng sau mỗi dòng có l, r. Với mỗi câu hỏi, in ra YES nếu đoạn sl…sr là xâu đối xứng, ngược lại NO.
Ví dụ:
| Nhập | In ra |
|---|---|
abacabad 31 72 46 8 | YESNONO |
Kiểm tra: nhập
racecarxabba 41 72 69 127 9Bài 4. Thứ tự từ điển của hai đoạn
Phần tiêu đề “Bài 4. Thứ tự từ điển của hai đoạn”Dòng đầu là xâu s (chữ cái thường, 1 ≤ |s| ≤ 2 × 105) và số q (1 ≤ q ≤ 2 × 105), q dòng sau mỗi dòng có a, b, c, d. Với mỗi câu hỏi, so sánh đoạn X = sa…sb với đoạn Y = sc…sd theo thứ tự từ điển, in <, = hoặc >.
Ví dụ:
| Nhập | In ra |
|---|---|
abacabad 31 3 5 71 2 5 84 4 2 2 | =<> |
Kiểm tra: nhập
bumbiibumbii 41 6 7 121 4 7 95 6 4 52 2 1 3Bài 5. Nhiều mẫu cùng độ dài
Phần tiêu đề “Bài 5. Nhiều mẫu cùng độ dài”Dòng đầu là xâu t (1 ≤ |t| ≤ 2 × 105), dòng thứ hai có k và L (1 ≤ k ≤ 105, 1 ≤ L ≤ |t|), k dòng sau mỗi dòng là một mẫu dài đúng L (chữ cái thường, k × L ≤ 106). Với mỗi mẫu, in ra số lần nó xuất hiện trong t.
Ví dụ:
| Nhập | In ra |
|---|---|
abababa3 2abbaaa | 330 |
Kiểm tra: nhập
bumbiibumbii4 3bummbibiixyzNhóm 2. Cấu trúc của một xâu
Phần tiêu đề “Nhóm 2. Cấu trúc của một xâu”Bài 6. Chu kỳ nhỏ nhất
Phần tiêu đề “Bài 6. Chu kỳ nhỏ nhất”Nhập xâu s (chữ cái thường, 1 ≤ |s| ≤ 106). In ra độ dài k nhỏ nhất sao cho s là một xâu độ dài k viết lặp lại nhiều lần (k phải là ước của |s|).
Ví dụ: nhập abcabcabc thì in ra 3.
Kiểm tra: nhập bumbiibumbiibumbiibumbii.
Bài 7. Tiền tố cũng là hậu tố
Phần tiêu đề “Bài 7. Tiền tố cũng là hậu tố”Nhập xâu s (chữ cái thường, 1 ≤ |s| ≤ 106). In ra, theo thứ tự tăng dần, mọi độ dài k (1 ≤ k nhỏ hơn |s|) mà k ký tự đầu của s giống hệt k ký tự cuối. Nếu không có, in ra -1.
Ví dụ: nhập abacaba thì in ra 1 3.
Kiểm tra: nhập aabaaabaabaa.
Bài 8. Xoay vòng
Phần tiêu đề “Bài 8. Xoay vòng”Nhập hai xâu s và t cùng độ dài (chữ cái thường, 1 ≤ độ dài ≤ 106) trên hai dòng. Xoay s nghĩa là chuyển một số ký tự đầu xuống cuối. In ra số lần xoay ít nhất (từ 0 đến |s| - 1 ký tự) để s thành t, hoặc -1 nếu không thể.
Ví dụ:
| Nhập | In ra |
|---|---|
abcdecdeab | 2 |
Kiểm tra: nhập
bumbiiiibumbNhóm 3. Đếm xâu con
Phần tiêu đề “Nhóm 3. Đếm xâu con”Bài 9. Số đoạn khác nhau có độ dài k
Phần tiêu đề “Bài 9. Số đoạn khác nhau có độ dài k”Nhập xâu s (chữ cái thường, 1 ≤ |s| ≤ 106) và số k (1 ≤ k ≤ |s|) trên hai dòng. In ra số xâu khác nhau trong các đoạn dài đúng k của s.
Ví dụ:
| Nhập | In ra |
|---|---|
abababa3 | 2 |
Kiểm tra: nhập
bumbiibumbiibumbii4Bài 10. Đếm mọi xâu con khác nhau
Phần tiêu đề “Bài 10. Đếm mọi xâu con khác nhau”Nhập xâu s (chữ cái thường, 1 ≤ |s| ≤ 2000). In ra số xâu con khác rỗng khác nhau của s (xâu con là đoạn liên tiếp).
Ví dụ: nhập abab thì in ra 7.
Kiểm tra: nhập bumbiiacademy.
Nhóm 4. Hash và tìm kiếm nhị phân
Phần tiêu đề “Nhóm 4. Hash và tìm kiếm nhị phân”Bài 11. Đoạn chung dài nhất của hai xâu
Phần tiêu đề “Bài 11. Đoạn chung dài nhất của hai xâu”Nhập hai xâu s, t trên hai dòng (chữ cái thường, mỗi xâu dài từ 1 đến 105). In ra độ dài đoạn liên tiếp dài nhất có mặt trong cả hai xâu.
Ví dụ:
| Nhập | In ra |
|---|---|
abcdxyzxyzabcd | 4 |
Kiểm tra: nhập
bumbiiacademyacademicbumbiiBài 12. Đoạn lặp lại dài nhất
Phần tiêu đề “Bài 12. Đoạn lặp lại dài nhất”Nhập xâu s (chữ cái thường, 1 ≤ |s| ≤ 105). In ra độ dài đoạn dài nhất xuất hiện ít nhất hai lần trong s (hai lần xuất hiện được chồng lên nhau), hoặc 0 nếu không có.
Ví dụ: nhập banana thì in ra 3.
Kiểm tra: nhập bumbiiacademybumbii.
Bài 13. Tìm mẫu sai tối đa một ký tự
Phần tiêu đề “Bài 13. Tìm mẫu sai tối đa một ký tự”Dòng đầu là xâu t, dòng thứ hai là mẫu p (chữ cái thường, 1 ≤ |p| ≤ |t| ≤ 2 × 105). In ra số vị trí i trong t mà đoạn dài |p| bắt đầu ở i khác p tối đa một ký tự.
Ví dụ:
| Nhập | In ra |
|---|---|
abcabcabdabc | 3 |
Kiểm tra: nhập
bumbiibumbiibumbiibumbieBài 14. Đoạn đối xứng dài nhất
Phần tiêu đề “Bài 14. Đoạn đối xứng dài nhất”Nhập xâu s (chữ cái thường, 1 ≤ |s| ≤ 2 × 105). In ra độ dài đoạn liên tiếp dài nhất của s là xâu đối xứng.
Ví dụ: nhập babad thì in ra 3.
Kiểm tra: nhập forgeeksskeegforabacabaxyz.