Rời rạc hóa (nén số)
Mảng đánh dấu cần một ô cho mỗi giá trị, nên không dùng được khi giá trị tới 109. Nhưng dãy chỉ có n = 105 số, tức là nhiều nhất 105 giá trị khác nhau. Trong rất nhiều bài, điều quan trọng chỉ là số nào lớn hơn số nào, không phải giá trị cụ thể. Rời rạc hóa (nén số) đổi các giá trị về 1, 2, 3, … mà vẫn giữ nguyên thứ tự lớn bé, sau đó dùng mảng như bình thường.
Nén số
Phần tiêu đề “Nén số”Dãy 1000 -7 500000000 1000 3:
- Lấy các giá trị khác nhau rồi sắp xếp tăng dần: -7, 3, 1000, 500000000.
- Mỗi số được thay bằng vị trí của nó trong danh sách này (đánh số từ 1).
| Số ban đầu | 1000 | -7 | 500000000 | 1000 | 3 |
|---|---|---|---|---|---|
| Sau khi nén | 3 | 1 | 4 | 3 | 2 |
Số nhỏ hơn vẫn được số nhỏ hơn, hai số bằng nhau vẫn bằng nhau. Mọi câu hỏi chỉ dựa vào thứ tự (so sánh, bằng nhau, đếm) cho cùng đáp số trên dãy mới, mà dãy mới chỉ có giá trị từ 1 đến n.
Tìm vị trí của một số trong danh sách đã sắp xếp dùng tìm kiếm nhị phân: bisect_left trong
Python, lower_bound trong C++, mỗi lần khoảng log2n bước.
import sysfrom bisect import bisect_leftdu_lieu = sys.stdin.read().split()n = int(du_lieu[0])a = list(map(int, du_lieu[1:1 + n]))cac_gia_tri = sorted(set(a)) # các giá trị khác nhau, tăng dầnnen = [bisect_left(cac_gia_tri, x) + 1 for x in a] # vị trí, đánh số từ 1print(*nen)#include <iostream>#include <vector>#include <algorithm>using namespace std;
int main() { int n; cin >> n; vector<long long> a(n); for (auto &x : a) cin >> x; vector<long long> cacGiaTri = a; sort(cacGiaTri.begin(), cacGiaTri.end()); cacGiaTri.erase(unique(cacGiaTri.begin(), cacGiaTri.end()), cacGiaTri.end()); // bỏ số trùng for (int i = 0; i < n; i++) { int viTri = lower_bound(cacGiaTri.begin(), cacGiaTri.end(), a[i]) - cacGiaTri.begin() + 1; cout << viTri << (i + 1 < n ? ' ' : '\n'); } return 0;}Cả bài mất khoảng n × log2n bước (sắp xếp và tìm kiếm nhị phân). Trong Python, thay vì
tìm kiếm nhị phân còn có thể tạo dict từ giá trị sang vị trí: vi_tri = {x: i + 1 for i, x in enumerate(cac_gia_tri)}.
Sau khi nén, đếm tần suất của giá trị lớn chỉ cần một mảng n ô: dem[nen[i]] += 1.
Đếm bằng tìm kiếm nhị phân
Phần tiêu đề “Đếm bằng tìm kiếm nhị phân”Trên dãy đã sắp xếp b, hai hàm tìm kiếm nhị phân cho biết:
bisect_left(b, x)(C++lower_bound): số phần tử nhỏ hơn x;bisect_right(b, x)(C++upper_bound): số phần tử không lớn hơn x.
Từ đó trả lời được nhiều câu hỏi, mỗi câu khoảng log2n bước:
| Câu hỏi | Cách tính |
|---|---|
| có bao nhiêu số bằng x? | bisect_right(b, x) - bisect_left(b, x) |
| có bao nhiêu số trong đoạn từ L đến R? | bisect_right(b, R) - bisect_left(b, L) |
| số nhỏ nhất không nhỏ hơn x? | b[bisect_left(b, x)] (nếu vị trí đó còn trong dãy) |
Dãy b = 2 4 4 4 7 9: số các số bằng 4 là 4 - 1 = 3, số các số trong đoạn từ 3 đến 8 là 5 - 1 = 4 (gồm 4, 4, 4, 7).
Đoạn thẳng trên trục số lớn: sự kiện
Phần tiêu đề “Đoạn thẳng trên trục số lớn: sự kiện”Có n đoạn [l, r] với tọa độ tới 109, hỏi có điểm nào bị nhiều đoạn phủ nhất. Không thể làm mảng 109 ô. Nhưng số đoạn phủ một điểm chỉ thay đổi ở đầu và cuối các đoạn, nên chỉ cần xét 2n vị trí đó.
Mỗi đoạn [l, r] (gồm cả hai đầu, tọa độ nguyên) tạo ra hai sự kiện:
- tại l: số đoạn đang phủ tăng 1;
- tại r + 1: số đoạn đang phủ giảm 1 (điểm r vẫn còn được phủ).
Sắp xếp các sự kiện theo tọa độ rồi đi từ trái sang phải, cộng dồn. Giá trị lớn nhất trong lúc cộng dồn là số đoạn phủ nhiều nhất. Cách này gọi là đường quét: tưởng tượng một đường thẳng quét từ trái sang phải, chỉ dừng lại ở các tọa độ có sự kiện.
Ba đoạn [1, 5], [3, 8], [6, 10]:
| Tọa độ | Sự kiện | Số đoạn đang phủ |
|---|---|---|
| 1 | +1 | 1 |
| 3 | +1 | 2 |
| 6 | -1 (đoạn [1, 5] hết), +1 | 2 |
| 9 | -1 | 1 |
| 11 | -1 | 0 |
Nhiều nhất là 2 đoạn.
import sysdu_lieu = sys.stdin.read().split()n = int(du_lieu[0])su_kien = []for i in range(n): l, r = int(du_lieu[1 + 2 * i]), int(du_lieu[2 + 2 * i]) su_kien.append((l, 1)) su_kien.append((r + 1, -1))su_kien.sort() # cùng tọa độ thì -1 đứng trước +1dang_phu = 0nhieu_nhat = 0for toa_do, thay_doi in su_kien: dang_phu += thay_doi nhieu_nhat = max(nhieu_nhat, dang_phu)print(nhieu_nhat)#include <iostream>#include <vector>#include <algorithm>using namespace std;
int main() { int n; cin >> n; vector<pair<long long, int>> suKien; for (int i = 0; i < n; i++) { long long l, r; cin >> l >> r; suKien.push_back({l, 1}); suKien.push_back({r + 1, -1}); } sort(suKien.begin(), suKien.end()); // cùng tọa độ thì -1 đứng trước +1 int dangPhu = 0, nhieuNhat = 0; for (auto [toaDo, thayDoi] : suKien) { dangPhu += thayDoi; nhieuNhat = max(nhieuNhat, dangPhu); } cout << nhieuNhat << "\n"; return 0;}Lưu ý: tại cùng một tọa độ, sự kiện -1 phải được xử lý trước sự kiện +1. Ở ví dụ trên, tại tọa độ 6 đoạn [1, 5] đã hết và đoạn [6, 10] mới bắt đầu, hai đoạn này không chung điểm nào. Nếu cộng +1 trước thì giữa chừng sẽ thấy 3 đoạn, sai. Sắp xếp cặp (tọa độ, thay đổi) tự đặt -1 trước +1 vì -1 nhỏ hơn 1.
Luyện tập
Phần tiêu đề “Luyện tập”Gồm 20 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. Nén số và xếp hạng
Phần tiêu đề “Nhóm 1. Nén số và xếp hạng”Bài 1. Nén dãy số
Phần tiêu đề “Bài 1. Nén dãy số”Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). Thay mỗi số bằng thứ tự của nó trong các giá trị khác nhau của dãy xếp tăng dần (số nhỏ nhất được 1). In ra dãy mới.
Ví dụ:
| Nhập | In ra |
|---|---|
51000 -7 500000000 1000 3 | 3 1 4 3 2 |
Kiểm tra: nhập
1042 -1000000000 42 7 1000000000 0 7 -5 42 0Bài 2. Bảng xếp hạng
Phần tiêu đề “Bài 2. Bảng xếp hạng”Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có điểm của n bạn theo thứ tự đăng ký (0 ≤ điểm ≤ 109). Hạng của một bạn bằng 1 cộng số bạn có điểm cao hơn bạn đó (bằng điểm thì cùng hạng). In ra hạng của từng bạn theo thứ tự đăng ký.
Ví dụ:
| Nhập | In ra |
|---|---|
680 95 80 70 95 60 | 3 1 3 5 1 6 |
Kiểm tra: nhập
10500 1000 0 500 750 1000 250 500 999 1Bài 3. Thứ tự của các từ
Phần tiêu đề “Bài 3. Thứ tự của các từ”Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có n từ gồm chữ cái thường (mỗi từ dài không quá 10 chữ). Thay mỗi từ bằng thứ tự của nó trong các từ khác nhau xếp theo thứ tự từ điển (từ đứng đầu được 1). In ra dãy số mới.
Ví dụ:
| Nhập | In ra |
|---|---|
6meo cho ga meo bo ga | 4 2 3 4 1 3 |
Kiểm tra: nhập
10tao tao cam le na buoi cam xoai buoi leBài 4. Cùng hình dạng
Phần tiêu đề “Bài 4. Cùng hình dạng”Hai dãy cùng độ dài n được gọi là cùng hình dạng nếu với mọi cặp vị trí i, j, quan hệ lớn hơn, bằng, nhỏ hơn giữa phần tử thứ i và thứ j ở hai dãy là giống nhau. Dòng đầu có n (1 ≤ n ≤ 105), hai dòng sau mỗi dòng có n số nguyên (|giá trị| ≤ 109). In ra YES nếu hai dãy cùng hình dạng, ngược lại in ra NO.
Ví dụ:
| Nhập | In ra |
|---|---|
510 30 20 30 52 9 7 9 -1 | YES |
Kiểm tra: nhập
64 4 8 1 6 8100 100 300 7 200 299Bài 5. Số xuất hiện nhiều nhất, giá trị lớn
Phần tiêu đề “Bài 5. Số xuất hiện nhiều nhất, giá trị lớn”Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). In ra số xuất hiện nhiều lần nhất và số lần đó. Nếu hòa, in số nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
71000000000 -3 1000000000 7 -3 -3 1000000000 | -3 3 |
Kiểm tra: nhập
10-999999999 5 5 123456789 -999999999 5 -999999999 0 123456789 123456789Bài 6. Giá trị chung của hai dãy
Phần tiêu đề “Bài 6. Giá trị chung của hai dãy”Dòng đầu có n, m (1 ≤ n, m ≤ 105). Hai dòng sau là dãy A gồm n số và dãy B gồm m số, chưa sắp xếp, có thể trùng (|giá trị| ≤ 109). In ra số giá trị khác nhau có mặt ở cả hai dãy.
Ví dụ:
| Nhập | In ra |
|---|---|
5 67 -2 7 100 55 5 3 -2 8 1000 | 2 |
Kiểm tra: nhập
8 71000000000 4 4 -9 0 13 -9 21-9 13 13 999999999 0 2 21Nhóm 2. Tìm kiếm nhị phân trên dãy đã sắp xếp
Phần tiêu đề “Nhóm 2. Tìm kiếm nhị phân trên dãy đã sắp xếp”Bài 7. Bao nhiêu giá trị không vượt quá x
Phần tiêu đề “Bài 7. Bao nhiêu giá trị không vượt quá x”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ó một số x. Với mỗi x, in ra trên một dòng số giá trị khác nhau của dãy không lớn hơn x.
Ví dụ:
| Nhập | In ra |
|---|---|
6 35 1 5 9 3 1490 | 240 |
Kiểm tra: nhập
10 4100 -50 100 7 7 7 2000000 -50 0 337-51100000000050Bài 8. Đếm số trong đoạn
Phần tiêu đề “Bài 8. Đếm số trong đ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 (L ≤ R). Với mỗi câu hỏi, in ra số phần tử của dãy có giá trị từ L đến R.
Ví dụ:
| Nhập | In ra |
|---|---|
6 32 4 9 4 7 43 85 6-10 100 | 406 |
Kiểm tra: nhập
10 415 -3 8 15 0 22 -3 8 8 10000000008 15-3 -316 21-1000000000 1000000000Bài 9. Mỗi số xuất hiện mấy lần
Phần tiêu đề “Bài 9. Mỗi số xuất hiện mấy lầ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ó một số x. Với mỗi x, in ra số lần x xuất hiện trong dãy.
Ví dụ:
| Nhập | In ra |
|---|---|
7 31000000000 3 3 -8 3 1000000000 0310000000005 | 320 |
Kiểm tra: nhập
10 4-1 -1 -1 2 2 999999999 -1 2 0 -1-120-2Bài 10. Số gần nhất
Phần tiêu đề “Bài 10. Số gần 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ó một số x. Với mỗi x, in ra số trong dãy gần x nhất. Nếu có hai số cách x bằng nhau, in số nhỏ hơn.
Ví dụ:
| Nhập | In ra |
|---|---|
5 410 3 25 17 31214100-5 | 1017253 |
Kiểm tra: nhập
8 5-100 40 7 7 1000000000 -3 22 61502-5250000002031Bài 11. Giá trị lớn thứ k
Phần tiêu đề “Bài 11. Giá trị lớn thứ k”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ó một số k (k ≥ 1). Với mỗi k, in ra giá trị lớn thứ k trong các giá trị khác nhau của dãy, hoặc NONE nếu dãy có ít hơn k giá trị khác nhau.
Ví dụ:
| Nhập | In ra |
|---|---|
7 34 9 4 1 9 7 2136 | 94NONE |
Kiểm tra: nhập
10 450 -20 50 50 13 -20 999 0 13 72671Nhóm 3. Đoạn thẳng trên trục số lớn
Phần tiêu đề “Nhóm 3. Đoạn thẳng trên trục số lớn”Bài 12. Tổng độ dài được phủ
Phần tiêu đề “Bài 12. Tổng độ dài được phủ”Trên trục số có n đoạn [li, ri] (độ dài đoạn là ri - li). Dòng đầu có n (1 ≤ n ≤ 105), n dòng sau mỗi dòng có l, r (0 ≤ l ≤ r ≤ 109). In ra tổng độ dài phần trục số được ít nhất một đoạn phủ.
Ví dụ:
| Nhập | In ra |
|---|---|
41 53 710 1211 11 | 8 |
Kiểm tra: nhập
6100 2000 50150 30040 601000000000 1000000000500 600Bài 13. Số nhóm đoạn
Phần tiêu đề “Bài 13. Số nhóm đoạn”Hai đoạn thẳng chung ít nhất một điểm thì thuộc cùng một nhóm, và nếu đoạn A cùng nhóm với B, B cùng nhóm với C thì A cùng nhóm với C. Dòng đầu có n (1 ≤ n ≤ 105), n dòng sau mỗi dòng có l, r (0 ≤ l ≤ r ≤ 109) là một đoạn [l, r]. In ra số nhóm.
Ví dụ:
| Nhập | In ra |
|---|---|
51 38 93 510 126 7 | 4 |
Kiểm tra: nhập
70 01 12 105 2020 2530 100000000026 29Bài 14. Khoảng trống lớn nhất
Phần tiêu đề “Bài 14. Khoảng trống lớn nhất”Có n cây cột điện trên một con đường thẳng, cột thứ i ở tọa độ xi (chưa sắp xếp, có thể trùng). Dòng đầu có n (2 ≤ n ≤ 105), dòng thứ hai có n số xi (0 ≤ xi ≤ 109). In ra khoảng cách lớn nhất giữa hai cột kề nhau trên đường (giữa chúng không có cột nào khác).
Ví dụ:
| Nhập | In ra |
|---|---|
530 5 12 100 60 | 40 |
Kiểm tra: nhập
81000000000 0 500 500 250000000 999999999 3 750000000Bài 15. Điểm bị phủ nhiều nhất
Phần tiêu đề “Bài 15. Điểm bị phủ nhiều nhất”Dòng đầu có n (1 ≤ n ≤ 105), n dòng sau mỗi dòng có l, r (0 ≤ l ≤ r ≤ 109) là một đoạn [l, r] gồm cả hai đầu. In ra số đoạn nhiều nhất cùng chứa một điểm nguyên.
Ví dụ:
| Nhập | In ra |
|---|---|
31 53 86 10 | 2 |
Kiểm tra: nhập
60 10000000005 53 99 205 910 15Bài 16. Số phòng khách sạn
Phần tiêu đề “Bài 16. Số phòng khách sạn”Khách thứ i đến khách sạn vào ngày ai và trả phòng vào ngày bi (ai nhỏ hơn bi): khách ở các đêm từ ngày ai đến trước ngày bi. Phòng được trả vào ngày bi dùng được cho khách đến đúng ngày đó. Dòng đầu có n (1 ≤ n ≤ 105), n dòng sau mỗi dòng có a, b (1 ≤ a, b ≤ 109). In ra số phòng ít nhất cần có.
Ví dụ:
| Nhập | In ra |
|---|---|
41 42 54 65 7 | 2 |
Kiểm tra: nhập
710 2015 2520 301 100000000025 2612 1313 15Bài 17. Mỗi điểm thuộc mấy đoạn
Phần tiêu đề “Bài 17. Mỗi điểm thuộc mấy đoạn”Dòng đầu có n, q (1 ≤ n, q ≤ 105), n dòng sau mỗi dòng có l, r (0 ≤ l ≤ r ≤ 109) là một đoạn [l, r], q dòng sau mỗi dòng có một điểm x. Với mỗi x, in ra số đoạn chứa x.
Ví dụ:
| Nhập | In ra |
|---|---|
3 41 53 86 1046110 | 2200 |
Kiểm tra: nhập
5 40 10050 5020 6060 100000000099 10150601001000000000Nhóm 4. Vận dụng
Phần tiêu đề “Nhóm 4. Vận dụng”Bài 18. Cộng đoạn, hỏi điểm
Phần tiêu đề “Bài 18. Cộng đoạn, hỏi điểm”Trục số ban đầu mọi điểm nguyên đều có giá trị 0. Có m thao tác, mỗi thao tác cộng v vào mọi điểm từ l đến r. Sau đó có q câu hỏi giá trị tại điểm x. Dòng đầu có m, q (1 ≤ m, q ≤ 105), m dòng sau mỗi dòng có l, r, v (0 ≤ l ≤ r ≤ 109, |v| ≤ 104), q dòng sau mỗi dòng có x (0 ≤ x ≤ 109). In ra giá trị tại từng điểm x.
Ví dụ:
| Nhập | In ra |
|---|---|
3 41 5 23 8 106 6 -14691 | 12902 |
Kiểm tra: nhập
4 50 1000000000 1100 200 5150 150 -7200 300 3150200201991000000000Bài 19. Điểm bị phủ ít nhất hai lần
Phần tiêu đề “Bài 19. Điểm bị phủ ít nhất hai lần”Dòng đầu có n (1 ≤ n ≤ 105), n dòng sau mỗi dòng có l, r (0 ≤ l ≤ r ≤ 109) là một đoạn [l, r] gồm cả hai đầu. In ra số điểm nguyên nằm trong ít nhất hai đoạn.
Ví dụ:
| Nhập | In ra |
|---|---|
31 53 86 10 | 6 |
Kiểm tra: nhập
50 100000000010 2015 301000000000 1000000000500 500Bài 20. Cặp điểm cùng hàng hoặc cùng cột
Phần tiêu đề “Bài 20. Cặp điểm cùng hàng hoặc cùng cột”Có n điểm trên mặt phẳng, điểm thứ i có tọa độ (xi, yi), có thể có điểm trùng nhau. Dòng đầu có n (1 ≤ n ≤ 105), n dòng sau mỗi dòng có x, y (|x|, |y| ≤ 109). In ra số cặp điểm (không kể thứ tự) có cùng hoành độ hoặc cùng tung độ.
Ví dụ:
| Nhập | In ra |
|---|---|
51 11 53 11 17 7 | 5 |
Kiểm tra: nhập
80 00 1000000000-1000000000 00 05 55 05 59 1000000000