Dãy con tăng dài nhất (LIS)
Từ dãy 3 1 4 1 5 9 2 6, xóa đi một số phần tử (giữ nguyên thứ tự các phần tử còn lại) để được dãy tăng ngặt dài nhất. Ví dụ 1 4 5 9 hoặc 3 4 5 6, dài 4. Đây là bài dãy con tăng dài nhất (LIS), bài mẫu quen thuộc nhất của quy hoạch động.
Quy hoạch động n2
Phần tiêu đề “Quy hoạch động n2”Gọi dai[i] là độ dài dãy con tăng dài nhất kết thúc tại ai. Dãy đó gồm ai đứng cuối, trước nó là một dãy con tăng kết thúc tại aj nào đó với j đứng trước i và aj nhỏ hơn ai (hoặc không có gì):
dai[i] = 1 + max(dai[j] với j nhỏ hơn i và aj nhỏ hơn ai) (max của tập rỗng coi là 0)
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| ai | 3 | 1 | 4 | 1 | 5 | 9 | 2 | 6 |
| dai[i] | 1 | 1 | 2 | 1 | 3 | 4 | 2 | 4 |
Đáp số là dai lớn nhất, 4. Mỗi i xét mọi j đứng trước, cả bài khoảng n2 / 2 bước: n = 5000 là khoảng 107, n = 105 thì quá chậm.
Cách n log n: đuôi nhỏ nhất
Phần tiêu đề “Cách n log n: đuôi nhỏ nhất”Giữ mảng duoi: duoi[k] là số cuối nhỏ nhất của một dãy con tăng dài k + 1 trong các phần tử đã
xét. Đuôi càng nhỏ càng dễ nối thêm, nên chỉ cần nhớ đuôi nhỏ nhất cho mỗi độ dài.
Mảng duoi luôn tăng ngặt (dãy dài hơn thì đuôi lớn hơn). Khi xét ai:
- tìm vị trí k đầu tiên có
duoi[k]≥ ai bằng tìm kiếm nhị phân; - nếu không có (ai lớn hơn mọi đuôi): nối ai vào cuối, độ dài tăng 1;
- nếu có: thay
duoi[k] = a[i](có dãy dài k + 1 với đuôi nhỏ hơn trước).
| ai | duoi sau khi xét |
|---|---|
| 3 | 3 |
| 1 | 1 |
| 4 | 1 4 |
| 1 | 1 4 |
| 5 | 1 4 5 |
| 9 | 1 4 5 9 |
| 2 | 1 2 5 9 |
| 6 | 1 2 5 6 |
Độ dài mảng duoi cuối cùng là đáp số, 4. Lưu ý mảng duoi không phải là một dãy con tăng
(1 2 5 6 không xuất hiện theo đúng thứ tự trong dãy), chỉ độ dài của nó là đúng.
import sysfrom bisect import bisect_leftdu_lieu = sys.stdin.read().split()n = int(du_lieu[0])duoi = []for x in map(int, du_lieu[1:1 + n]): k = bisect_left(duoi, x) # vị trí đầu tiên có duoi[k] >= x if k == len(duoi): duoi.append(x) else: duoi[k] = xprint(len(duoi))#include <iostream>#include <vector>#include <algorithm>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<long long> duoi; for (int i = 0; i < n; i++) { long long x; cin >> x; auto it = lower_bound(duoi.begin(), duoi.end(), x); // duoi[k] >= x đầu tiên if (it == duoi.end()) duoi.push_back(x); else *it = x; } cout << duoi.size() << "\n"; return 0;}Mỗi phần tử một lần tìm kiếm nhị phân, cả bài khoảng n × log2n bước.
Các biến thể
Phần tiêu đề “Các biến thể”| Đề hỏi | Thay đổi |
|---|---|
| dãy con không giảm dài nhất | dùng bisect_right (C++ upper_bound) thay bisect_left |
| dãy con giảm dài nhất | đổi dấu mọi số rồi tìm dãy con tăng |
| ít nhất phải xóa mấy số để dãy tăng | n - (độ dài dãy con tăng dài nhất) |
| cặp (rộng, cao), xếp lồng nhau | sắp xếp theo rộng tăng dần, rộng bằng nhau thì cao giảm dần, rồi tìm dãy con tăng theo cao |
Với cặp (rộng, cao), cao giảm dần khi rộng bằng nhau để hai hộp cùng rộng không bị chọn lồng vào nhau.
Luyện tập
Phần tiêu đề “Luyện tập”Gồm 16 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. Độ dài dãy con tăng
Phần tiêu đề “Nhóm 1. Độ dài dãy con tăng”Bài 1. Độ dài dãy con tăng dài nhất
Phần tiêu đề “Bài 1. Độ dài dãy con tăng dài nhất”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). In ra độ dài dãy con tăng ngặt dài nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
83 1 4 1 5 9 2 6 | 4 |
Kiểm tra: nhập
1210 -3 7 7 2 8 -1 9 4 5 6 1Bài 2. Dãy con không giảm dài nhất
Phần tiêu đề “Bài 2. Dãy con không giảm dài nhất”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). In ra độ dài dãy con không giảm dài nhất (cho phép hai số liền nhau bằng nhau).
Ví dụ:
| Nhập | In ra |
|---|---|
72 2 1 3 3 2 4 | 5 |
Kiểm tra: nhập
105 5 5 1 1 6 1 5 5 2Bài 3. Dãy con giảm dài nhất
Phần tiêu đề “Bài 3. Dãy con giảm dài nhất”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). In ra độ dài dãy con giảm ngặt dài nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
75 8 3 7 2 6 1 | 4 |
Kiểm tra: nhập
10-1 4 4 3 9 0 -2 5 -3 -8Bài 4. Độ dài dãy con tăng kết thúc tại từng vị trí
Phần tiêu đề “Bài 4. Độ dài dãy con tăng kết thúc tại từng vị trí”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). Với mỗi i, in ra độ dài dãy con tăng ngặt dài nhất kết thúc tại ai.
Ví dụ:
| Nhập | In ra |
|---|---|
83 1 4 1 5 9 2 6 | 1 1 2 1 3 4 2 4 |
Kiểm tra: nhập
104 2 6 3 5 1 7 7 0 8Bài 5. Xóa ít nhất để dãy tăng
Phần tiêu đề “Bài 5. Xóa ít nhất để dãy tăng”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). In ra số phần tử ít nhất cần xóa để các phần tử còn lại tạo thành dãy tăng ngặt.
Ví dụ:
| Nhập | In ra |
|---|---|
64 2 5 3 6 1 | 3 |
Kiểm tra: nhập
101 9 2 8 3 7 4 6 5 5Nhóm 2. Biến đổi về dãy con tăng
Phần tiêu đề “Nhóm 2. Biến đổi về dãy con tăng”Bài 6. Sửa ít nhất để dãy tăng ngặt
Phần tiêu đề “Bài 6. Sửa ít nhất để dãy tăng ngặt”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). Mỗi lần được đổi một phần tử thành số nguyên bất kỳ. In ra số lần đổi ít nhất để dãy tăng ngặt.
Ví dụ:
| Nhập | In ra |
|---|---|
51 2 2 3 4 | 2 |
Kiểm tra: nhập
85 1 2 3 10 4 5 6Bài 7. Cầu không cắt nhau
Phần tiêu đề “Bài 7. Cầu không cắt nhau”Hai bờ sông song song. Có n cặp thành phố muốn nối cầu, cặp thứ i nối điểm ai ở bờ trái với điểm bi ở bờ phải (các ai khác nhau, các bi khác nhau). Hai cây cầu cắt nhau nếu ai nhỏ hơn aj nhưng bi lớn hơn bj. Dòng đầu có n (1 ≤ n ≤ 2 × 105), n dòng sau mỗi dòng có a, b (1 ≤ a, b ≤ 109). In ra số cầu nhiều nhất xây được mà không có hai cầu nào cắt nhau.
Ví dụ:
| Nhập | In ra |
|---|---|
51 32 13 44 25 5 | 3 |
Kiểm tra: nhập
88 13 61 25 42 77 84 36 5Bài 8. Xếp hộp lồng nhau
Phần tiêu đề “Bài 8. Xếp hộp lồng nhau”Có n hộp, hộp thứ i có đáy hình chữ nhật rộng wi, dài hi (không được xoay). Hộp A bỏ vừa vào hộp B nếu wA nhỏ hơn wB và hA nhỏ hơn hB. Dòng đầu có n (1 ≤ n ≤ 2 × 105), n dòng sau mỗi dòng có w, h (1 ≤ w, h ≤ 109). In ra số hộp nhiều nhất lồng được vào nhau thành một chồng.
Ví dụ:
| Nhập | In ra |
|---|---|
45 46 46 72 3 | 3 |
Kiểm tra: nhập
83 33 54 44 65 52 86 71 1Bài 9. Ít cột nhất
Phần tiêu đề “Bài 9. Ít cột nhất”Có n tấm thẻ đến lần lượt, tấm thứ i ghi số ai. Mỗi tấm được đặt lên trên cùng một cột: tấm mới phải ghi số nhỏ hơn hoặc bằng tấm đang trên cùng cột đó, hoặc lập một cột mới. Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số ai (|ai| ≤ 109). In ra số cột ít nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
75 2 6 1 3 4 2 | 3 |
Kiểm tra: nhập
103 3 3 1 4 1 5 9 2 6Nhóm 3. Kết hợp hai chiều
Phần tiêu đề “Nhóm 3. Kết hợp hai chiều”Bài 10. Dãy con hình núi
Phần tiêu đề “Bài 10. Dãy con hình núi”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). Dãy con hình núi là dãy con tăng ngặt rồi giảm ngặt (phần tăng hoặc phần giảm có thể chỉ gồm một số là đỉnh). In ra độ dài dãy con hình núi dài nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
81 11 2 10 4 5 2 1 | 6 |
Kiểm tra: nhập
103 1 4 1 5 9 2 6 5 3Bài 11. Dãy con tăng với bước nhảy không quá k
Phần tiêu đề “Bài 11. Dãy con tăng với bước nhảy không quá k”Dòng đầu có n, k (1 ≤ n ≤ 3000, 1 ≤ k ≤ 109), dòng thứ hai có n số nguyên (|ai| ≤ 109). Tìm dãy con tăng ngặt dài nhất mà hai số liền nhau trong dãy con chênh nhau không quá k. In ra độ dài của nó.
Ví dụ:
| Nhập | In ra |
|---|---|
7 21 5 2 3 6 4 8 | 4 |
Kiểm tra: nhập
10 310 1 4 2 7 5 8 11 9 12Bài 12. Dãy con chung tăng dài nhất
Phần tiêu đề “Bài 12. Dãy con chung tăng dài nhất”Dòng đầu có n, m (1 ≤ n, m ≤ 3000), dòng thứ hai có n số của dãy A, dòng thứ ba có m số của dãy B (|giá trị| ≤ 109). In ra độ dài dãy dài nhất vừa là dãy con của A, vừa là dãy con của B, và tăng ngặt.
Ví dụ:
| Nhập | In ra |
|---|---|
5 52 3 1 6 53 1 2 6 5 | 2 |
Kiểm tra: nhập
8 71 4 2 5 3 6 7 24 1 5 2 6 3 7Nhóm 4. Truy vết, đếm, tổng
Phần tiêu đề “Nhóm 4. Truy vết, đếm, tổng”Bài 13. In ra dãy con tăng dài nhất
Phần tiêu đề “Bài 13. In ra dãy con tăng dài nhất”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). In ra độ dài dãy con tăng ngặt dài nhất trên dòng đầu, dòng thứ hai in ra các vị trí (đánh số từ 1) của một dãy như vậy. Nếu có nhiều dãy, in dãy có vị trí cuối cùng lớn nhất; nếu vẫn còn nhiều, chọn dãy có vị trí áp chót lớn nhất, và cứ thế đi ngược về đầu.
Ví dụ:
| Nhập | In ra |
|---|---|
83 1 4 1 5 9 2 6 | 42 3 5 8 |
Kiểm tra: nhập
102 7 1 8 2 8 1 8 2 8Bài 14. Đếm số dãy con tăng dài nhất
Phần tiêu đề “Bài 14. Đếm số dãy con tăng dài nhất”Dòng đầu có n (1 ≤ n ≤ 3000), dòng thứ hai có n số nguyên (|ai| ≤ 109). In ra số dãy con tăng ngặt có độ dài lớn nhất (hai dãy con khác nhau nếu tập vị trí khác nhau), chia lấy dư 109 + 7.
Ví dụ:
| Nhập | In ra |
|---|---|
61 3 2 4 3 5 | 3 |
Kiểm tra: nhập
102 2 2 2 2 2 2 2 2 2Bài 15. Dãy con tăng có tổng lớn nhất
Phần tiêu đề “Bài 15. Dãy con tăng có tổng lớn nhất”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên dương (ai ≤ 109). In ra tổng lớn nhất của một dãy con tăng ngặt (không cần dài nhất).
Ví dụ:
| Nhập | In ra |
|---|---|
61 101 2 3 100 4 | 106 |
Kiểm tra: nhập
105 3 8 1 9 2 10 4 7 6Bài 16. Dãy con lên xuống dài nhất
Phần tiêu đề “Bài 16. Dãy con lên xuống dài nhất”Dòng đầu có n (1 ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (|ai| ≤ 109). Dãy con lên xuống là dãy con mà các hiệu giữa hai số liền nhau đổi dấu luân phiên (dương, âm, dương… hoặc âm, dương, âm…; không có hiệu bằng 0). Dãy một số cũng là dãy lên xuống. In ra độ dài dãy con lên xuống dài nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
61 7 4 9 2 5 | 6 |
Kiểm tra: nhập
101 17 5 10 13 15 10 5 16 8