Bảng thưa (Sparse Table)
Có 105 câu hỏi “số nhỏ nhất từ vị trí l đến r là bao nhiêu?”. Tổng tiền tố trả lời tổng đoạn bằng một phép trừ, nhưng nhỏ nhất không trừ được: biết nhỏ nhất của 10 số đầu và của 3 số đầu không suy ra được nhỏ nhất của 7 số còn lại. Bảng thưa chuẩn bị trước nhỏ nhất của các đoạn có độ dài là lũy thừa của 2, rồi mỗi câu hỏi ghép từ hai đoạn như vậy.
Chuẩn bị bảng
Phần tiêu đề “Chuẩn bị bảng”Gọi bang[j][i] là số nhỏ nhất của đoạn dài 2j bắt đầu tại i, tức là từ ai đến
ai + 2j - 1.
- j = 0: đoạn dài 1,
bang[0][i] = a[i]. - j lớn hơn 0: đoạn dài 2j gồm hai nửa dài 2j - 1, nên
bang[j][i] = min(bang[j-1][i], bang[j-1][i + 2^(j-1)]).
Dãy 5 2 4 7 1 3 6 8:
| j (độ dài) | i = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 0 (1) | 5 | 2 | 4 | 7 | 1 | 3 | 6 | 8 |
| 1 (2) | 2 | 2 | 4 | 1 | 1 | 3 | 6 | |
| 2 (4) | 2 | 1 | 1 | 1 | 1 | |||
| 3 (8) | 1 |
Có khoảng log2n hàng, mỗi hàng n ô, chuẩn bị mất khoảng n × log2n bước (n = 105 là khoảng 1,7 × 106).
Trả lời câu hỏi
Phần tiêu đề “Trả lời câu hỏi”Đoạn [l, r] dài len = r - l + 1. Lấy k lớn nhất mà 2k ≤ len. Hai đoạn dài 2k, một bắt đầu tại l, một kết thúc tại r, phủ kín [l, r] (vì 2k + 2k ≥ len). Hai đoạn có thể chồng lên nhau, nhưng với phép nhỏ nhất thì một số bị xét hai lần không làm sai kết quả.
min(l, r) = min(bang[k][l], bang[k][r - 2k + 1])
Đoạn từ 1 đến 6 (2 4 7 1 3 6) dài 6, k = 2: min(bang[2][1], bang[2][3]) = min(1, 1) = 1.
import sysdu_lieu = sys.stdin.read().split()n, q = int(du_lieu[0]), int(du_lieu[1])a = list(map(int, du_lieu[2:2 + n]))bang = [a]j = 1while (1 << j) <= n: truoc = bang[-1] nua = 1 << (j - 1) bang.append([min(truoc[i], truoc[i + nua]) for i in range(n - (1 << j) + 1)]) j += 1ket_qua = []vi_tri = 2 + nfor _ in range(q): l, r = int(du_lieu[vi_tri]) - 1, int(du_lieu[vi_tri + 1]) - 1 # đề đánh số từ 1 vi_tri += 2 k = (r - l + 1).bit_length() - 1 # 2^k ≤ độ dài ket_qua.append(min(bang[k][l], bang[k][r - (1 << k) + 1]))print("\n".join(map(str, ket_qua)))#include <iostream>#include <vector>#include <algorithm>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, q; cin >> n >> q; vector<vector<long long>> bang(1, vector<long long>(n)); for (auto &x : bang[0]) cin >> x; for (int j = 1; (1 << j) <= n; j++) { int nua = 1 << (j - 1); bang.push_back(vector<long long>(n - (1 << j) + 1)); for (int i = 0; i + (1 << j) <= n; i++) bang[j][i] = min(bang[j - 1][i], bang[j - 1][i + nua]); } while (q--) { int l, r; cin >> l >> r; l--, r--; // đề đánh số từ 1 int k = 31 - __builtin_clz(r - l + 1); // 2^k ≤ độ dài cout << min(bang[k][l], bang[k][r - (1 << k) + 1]) << "\n"; } return 0;}Mỗi câu hỏi chỉ tốn một phép so sánh. Trong C++, 31 - __builtin_clz(x) là số mũ của lũy thừa 2 lớn
nhất không vượt quá x; trong Python là x.bit_length() - 1.
Phép toán nào dùng được?
Phần tiêu đề “Phép toán nào dùng được?”Bảng thưa trả lời bằng hai đoạn chồng lên nhau, nên chỉ dùng cho phép toán mà xét một số hai lần không làm sai: f(x, x) = x.
| Phép toán | Dùng được? | Vì sao |
|---|---|---|
| nhỏ nhất, lớn nhất | được | min(x, x) = x |
| ƯCLN | được | ƯCLN(x, x) = x |
| AND, OR theo bit | được | x AND x = x |
| tổng, tích, XOR | không | x + x khác x: phần chồng bị cộng hai lần |
Với tổng thì dùng tổng tiền tố. Bảng thưa không cập nhật được khi dãy thay đổi; dãy thay đổi thì dùng cây Fenwick hoặc cây phân đoạn.
Nhảy nhị phân
Phần tiêu đề “Nhảy nhị phân”Ý tưởng “lưu kết quả cho các bước dài 2j” còn dùng cho câu hỏi nhảy k bước: mỗi vị trí
i có một vị trí tiếp theo tiep[i], hỏi sau k bước (k tới 1018) đang ở đâu. Lưu
nhay[j][i] là vị trí sau 2j bước từ i, với nhay[j][i] = nhay[j-1][nhay[j-1][i]]. Viết k
dưới dạng nhị phân, với mỗi bit 1 thứ j thì nhảy
2j bước. Cách này còn gọi là binary lifting, dùng nhiều ở bài tổ tiên chung gần nhất.
Luyện tập
Phần tiêu đề “Luyện tập”Gồm 17 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. Câu hỏi trên đoạn
Phần tiêu đề “Nhóm 1. Câu hỏi trên đoạn”Bài 1. Nhỏ nhất trên đoạn
Phần tiêu đề “Bài 1. Nhỏ nhất trên đoạn”Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có n số nguyên (|ai| ≤ 109), q dòng sau mỗi dòng có l, r (1 ≤ l ≤ r ≤ n). Với mỗi câu hỏi, in ra số nhỏ nhất từ al đến ar.
Ví dụ:
| Nhập | In ra |
|---|---|
8 35 2 4 7 1 3 6 82 73 46 8 | 143 |
Kiểm tra: nhập
10 49 -3 7 7 0 12 -5 4 4 11 34 61 108 10Bài 2. Mực nước cao nhất
Phần tiêu đề “Bài 2. Mực nước cao nhất”Một trạm đo ghi mực nước sông trong n ngày. Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có n số (0 ≤ mực nước ≤ 109), q dòng sau mỗi dòng có l, r. Với mỗi câu hỏi, in ra mực nước cao nhất từ ngày l đến ngày r.
Ví dụ:
| Nhập | In ra |
|---|---|
7 312 15 11 18 14 13 161 32 65 7 | 151816 |
Kiểm tra: nhập
10 4300 120 450 450 90 610 200 610 50 4001 23 56 89 10Bài 3. Biên độ nhiệt
Phần tiêu đề “Bài 3. Biên độ nhiệt”Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có nhiệt độ n ngày (|ti| ≤ 100), q dòng sau mỗi dòng có l, r. Với mỗi câu hỏi, in ra (nhiệt độ cao nhất) - (nhiệt độ thấp nhất) từ ngày l đến ngày r.
Ví dụ:
| Nhập | In ra |
|---|---|
6 320 25 18 30 22 191 32 54 4 | 7120 |
Kiểm tra: nhập
10 4-5 3 -12 8 8 0 -1 15 -20 71 45 71 108 9Bài 4. ƯCLN trên đoạn
Phần tiêu đề “Bài 4. ƯCLN trên đoạn”Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có n số nguyên dương (ai ≤ 109), q dòng sau mỗi dòng có l, r. Với mỗi câu hỏi, in ra ƯCLN của al, …, ar.
Ví dụ:
| Nhập | In ra |
|---|---|
6 312 18 24 9 27 61 33 54 6 | 633 |
Kiểm tra: nhập
10 4100 75 50 125 30 45 60 7 14 211 42 68 101 10Bài 5. AND và OR trên đoạn
Phần tiêu đề “Bài 5. AND và OR trên đoạn”Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có n số nguyên (0 ≤ ai nhỏ hơn 230), q dòng sau mỗi dòng có l, r. Với mỗi câu hỏi, in ra trên một dòng hai số: AND và OR của al, …, ar.
Ví dụ:
| Nhập | In ra |
|---|---|
5 312 10 14 7 151 22 41 5 | 8 142 150 15 |
Kiểm tra: nhập
8 4255 254 127 128 64 192 3 11 34 66 81 8Bài 6. Vị trí số nhỏ nhất
Phần tiêu đề “Bài 6. Vị trí số nhỏ nhất”Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có n số nguyên (|ai| ≤ 109), q dòng sau mỗi dòng có l, r. Với mỗi câu hỏi, in ra vị trí của số nhỏ nhất trong đoạn từ l đến r (đánh số từ 1); có nhiều vị trí thì in vị trí nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
7 34 2 6 2 5 1 31 53 51 7 | 246 |
Kiểm tra: nhập
10 47 3 9 3 3 8 1 6 1 21 42 56 103 3Bài 7. Độ dốc lớn nhất
Phần tiêu đề “Bài 7. Độ dốc lớn nhất”Đường đi bộ có n cột mốc, độ cao cột thứ i là hi. Độ dốc giữa hai cột liền nhau là |hi + 1 - hi|. Dòng đầu có n, q (2 ≤ n ≤ 105, 1 ≤ q ≤ 105), dòng thứ hai có n số hi (0 ≤ hi ≤ 109), q dòng sau mỗi dòng có l, r (1 ≤ l nhỏ hơn r ≤ n). Với mỗi câu hỏi, in ra độ dốc lớn nhất khi đi từ cột l đến cột r.
Ví dụ:
| Nhập | In ra |
|---|---|
6 310 13 9 9 20 181 33 41 6 | 4011 |
Kiểm tra: nhập
10 4100 90 95 150 140 141 300 290 285 3001 33 66 81 10Bài 8. Nhỏ nhất trên vòng tròn
Phần tiêu đề “Bài 8. Nhỏ nhất trên vòng tròn”n số xếp thành vòng tròn, sau số thứ n là số thứ 1. Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có n số nguyên (|ai| ≤ 109), q dòng sau mỗi dòng có l, r (1 ≤ l, r ≤ n). Với mỗi câu hỏi, đi theo vòng từ vị trí l đến vị trí r (nếu l lớn hơn r thì đi qua vị trí n rồi về 1), in ra số nhỏ nhất đã đi qua.
Ví dụ:
| Nhập | In ra |
|---|---|
6 35 9 2 7 4 82 45 16 6 | 248 |
Kiểm tra: nhập
8 43 -1 4 1 -5 9 2 67 23 68 11 8Bài 9. Số chia hết cho mọi số trong đoạn
Phần tiêu đề “Bài 9. Số chia hết cho mọi số trong đoạn”Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có n số nguyên dương (ai ≤ 109), q dòng sau mỗi dòng có l, r. Với mỗi câu hỏi, in ra có bao nhiêu vị trí i trong đoạn từ l đến r mà mọi số trong đoạn đều chia hết cho ai.
Ví dụ:
| Nhập | In ra |
|---|---|
5 32 4 2 6 31 42 52 2 | 201 |
Kiểm tra: nhập
10 46 12 6 18 6 3 9 3 3 271 51 106 92 4Nhóm 2. Bảng thưa và tìm kiếm nhị phân
Phần tiêu đề “Nhóm 2. Bảng thưa và tìm kiếm nhị phân”Bài 10. Đếm đoạn có ƯCLN bằng 1
Phần tiêu đề “Bài 10. Đếm đoạn có ƯCLN bằng 1”Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có n số nguyên dương (ai ≤ 109). In ra số đoạn liên tiếp có ƯCLN các phần tử bằng 1.
Ví dụ:
| Nhập | In ra |
|---|---|
42 3 4 6 | 5 |
Kiểm tra: nhập
106 10 15 4 8 9 12 7 14 21Bài 11. Đoạn dài nhất có ƯCLN lớn hơn 1
Phần tiêu đề “Bài 11. Đoạn dài nhất có ƯCLN lớn hơn 1”Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có n số nguyên dương (ai ≤ 109). In ra độ dài lớn nhất của một đoạn liên tiếp có ƯCLN các phần tử lớn hơn 1, hoặc 0 nếu không có.
Ví dụ:
| Nhập | In ra |
|---|---|
63 6 4 8 12 5 | 4 |
Kiểm tra: nhập
107 14 21 2 6 9 15 25 10 1Bài 12. Đoạn ngắn nhất có ƯCLN bằng ƯCLN cả dãy
Phần tiêu đề “Bài 12. Đoạn ngắn nhất có ƯCLN bằng ƯCLN cả dãy”Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có n số nguyên dương (ai ≤ 109). Gọi G là ƯCLN của cả dãy. In ra độ dài nhỏ nhất của một đoạn liên tiếp có ƯCLN bằng G.
Ví dụ:
| Nhập | In ra |
|---|---|
512 18 8 30 20 | 2 |
Kiểm tra: nhập
1060 84 36 90 150 42 70 105 140 210Bài 13. Đếm đoạn có AND bằng k
Phần tiêu đề “Bài 13. Đếm đoạn có AND bằng k”Dòng đầu có n, k (1 ≤ n ≤ 105, 0 ≤ k nhỏ hơn 230), dòng thứ hai có n số nguyên (0 ≤ ai nhỏ hơn 230). In ra số đoạn liên tiếp có AND các phần tử bằng k.
Ví dụ:
| Nhập | In ra |
|---|---|
4 27 6 3 2 | 6 |
Kiểm tra: nhập
10 412 13 4 6 15 7 5 4 12 28Bài 14. Điểm cao nhất trong đoạn
Phần tiêu đề “Bài 14. Điểm cao nhất trong đoạn”Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có điểm của n vận động viên theo số áo 1, 2, …, n (0 ≤ điểm ≤ 109), q dòng sau mỗi dòng có l, r. Với mỗi câu hỏi, in ra số áo của vận động viên điểm cao nhất trong các số áo từ l đến r; nếu nhiều người bằng điểm thì in số áo lớn nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
6 38 9 7 9 6 91 31 55 6 | 246 |
Kiểm tra: nhập
10 45 5 2 8 8 3 1 8 7 01 23 71 109 10Nhóm 3. Bảng thưa hai chiều
Phần tiêu đề “Nhóm 3. Bảng thưa hai chiều”Bài 15. Nhỏ nhất trên hình chữ nhật
Phần tiêu đề “Bài 15. Nhỏ nhất trên hình chữ nhật”Bảng có m hàng, n cột. Dòng đầu có m, n, q (1 ≤ m, n ≤ 300, 1 ≤ q ≤ 105), m dòng sau mỗi dòng có n số nguyên (|giá trị| ≤ 109), q dòng sau mỗi dòng có r1, c1, r2, c2 (r1 ≤ r2, c1 ≤ c2). Với mỗi câu hỏi, in ra số nhỏ nhất trong hình chữ nhật từ hàng r1 đến r2, cột c1 đến c2.
Ví dụ:
| Nhập | In ra |
|---|---|
3 4 25 8 2 67 1 9 43 6 5 81 1 2 22 3 3 4 | 14 |
Kiểm tra: nhập
4 5 39 4 7 3 82 6 1 5 98 3 7 2 45 9 6 1 31 1 2 33 4 4 51 1 4 5Nhóm 4. Nhảy nhị phân
Phần tiêu đề “Nhóm 4. Nhảy nhị phân”Bài 16. Nhảy k bước
Phần tiêu đề “Bài 16. Nhảy k bước”Có n ô đánh số 1 đến n, từ ô i luôn nhảy sang ô ti. Dòng đầu có n, q (1 ≤ n, q ≤ 105), dòng thứ hai có n số ti (1 ≤ ti ≤ n), q dòng sau mỗi dòng có x, k (1 ≤ x ≤ n, 0 ≤ k ≤ 1018). Với mỗi câu hỏi, in ra ô đang đứng sau k lần nhảy bắt đầu từ ô x.
Ví dụ:
| Nhập | In ra |
|---|---|
5 32 3 1 5 41 44 32 0 | 252 |
Kiểm tra: nhập
8 43 5 6 2 8 1 4 71 10000000000000000002 77 123458 1Bài 17. Chia đoạn thành ít khúc nhất
Phần tiêu đề “Bài 17. Chia đoạn thành ít khúc nhất”Dòng đầu có n, S, q (1 ≤ n, q ≤ 105, 1 ≤ S ≤ 1014), dòng thứ hai có n số nguyên dương (ai ≤ S), q dòng sau mỗi dòng có l, r. Với mỗi câu hỏi, in ra số khúc ít nhất khi chia đoạn al, …, ar thành các khúc liên tiếp, mỗi khúc có tổng không quá S.
Ví dụ:
| Nhập | In ra |
|---|---|
7 10 34 5 3 6 2 7 11 72 46 6 | 321 |
Kiểm tra: nhập
10 15 48 7 3 9 5 6 10 2 4 121 103 62 97 8