Đoạn K
Đề bài
Mảng $A$ gồm $N$ phần tử là hoán vị của các giá trị từ 1 đến $N$. Bạn được phép chia mảng $A$ thành $K$ đoạn, trong đó mỗi đoạn gồm các phần tử liên tiếp có độ dài tùy ý (có thể khác nhau). Ví dụ một phép chia thành 5 đoạn:
Sau khi chia thành $K$ đoạn, bạn được phép sắp xếp các giá trị bên trong mỗi đoạn (độc lập với nhau).
Yêu cầu: hãy xác định số đoạn $K$ lớn nhất có thể sao cho sau khi sắp xếp xong thì mảng $A$ trở thành mảng được sắp xếp tăng dần.
Dữ liệu:
- Dòng đầu ghi số nguyên $N$ là số lượng phần tử của mảng $(1 \le N \le 10^6)$.
- Dòng tiếp theo ghi $N$ số nguyên $A_i$ là giá trị của các phần tử trong mảng $(1 \le A_i \le N)$.
Kết quả: một giá trị duy nhất là số lượng $K$ theo yêu cầu.
Giải thích:
- Ví dụ 1: chỉ có thể chia thành đúng một đoạn gồm tất cả các phần tử rồi sắp xếp: $[4\ 5\ 3\ 2\ 1] \to [1\ 2\ 3\ 4\ 5]$.
- Ví dụ 2: chia được nhiều nhất 3 đoạn: $[1]\ [2]\ [4\ 5\ 3] \to [1]\ [2]\ [3\ 4\ 5]$ – mảng đã được sắp xếp.
- Ví dụ 3: chia được nhiều nhất 2 đoạn: $[4\ 2\ 1\ 5\ 9\ 8\ 7\ 6\ 3]\ [10]$.
Giới hạn: 20% số test đầu có $N \le 20$; 20% tiếp theo có $N \le 10^2$; 20% tiếp theo có $N \le 10^4$; 20% tiếp theo có $N \le 10^5$; 20% còn lại có $N \le 10^6$.
5
4 5 3 2 115
1 2 4 5 3310
4 2 1 5 9 8 7 6 3 102Lời mở đầu
Một bài rất gọn nhưng cần "nhìn ra" một tính chất: chỗ nào được phép cắt? Khi trả lời được câu đó, ta chỉ việc tham lam cắt ở mọi chỗ được phép. Mình sẽ đi từ cách $O(N^2)$ (ăn 60% điểm) tới cách $O(N)$ full điểm.
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJLộ trình lời giải, bấm vào từng phần để nhảy tới:
Bộ testBộ test chính thức có gì?
Bộ test có 100 test, chia đúng 5 mức của đề (mỗi mức 20 test):
| Test | N | Đúng là hoán vị? | Đáp án K | Số test K = 1 |
|---|---|---|---|---|
| 1–20 | 4 … 20 | Có | 1 … 17 | 2/20 |
| 21–40 | 100 | Có | 2 … 32 | 0/20 |
| 41–60 | 10.000 | Có | 8 … 32 | 0/20 |
| 61–80 | 100.000 | Có | 2 … 50 | 0/20 |
| 81–100 | 1.000.000 | Có | 6 … 36 | 0/20 |
- Mình đã kiểm tra: mọi test đều đúng là hoán vị của $1..N$ như đề nói.
- Test 1–3 là 3 ví dụ của đề. Đáp án nhìn chung khá nhỏ (nhiều nhất 50) – các hoán vị ngẫu nhiên rất ít chỗ cắt được.
Đối chiếu giới hạn với test thật và số test đúng của từng cách:
| Subtask | Đề ghi | Test thật | Cách 1 $O(N^2)$ | Cách 2 $O(N)$ |
|---|---|---|---|---|
| 20% | $N \le 20$ | 1–20 | 20/20 | 20/20 |
| 20% | $N \le 10^2$ | 21–40 | 20/20 | 20/20 |
| 20% | $N \le 10^4$ | 41–60 | 20/20 | 20/20 |
| 20% | $N \le 10^5$ | 61–80 | 0/20 | 20/20 |
| 20% | $N \le 10^6$ | 81–100 | 0/20 | 20/20 |
Ý tưởngCắt được sau vị trí i khi max(A[1..i]) = i
Sau khi sắp xếp trong từng đoạn, mảng phải là $1, 2, \ldots, N$. Xét một chỗ cắt ngay sau vị trí $i$: mọi phần tử bên trái chỗ cắt không bao giờ sang được bên phải (sắp xếp chỉ diễn ra trong từng đoạn). Vì vậy:
Vế sau đúng vì $A$ là hoán vị: $i$ số khác nhau mà số lớn nhất là $i$ thì chúng phải đúng là $1..i$.
Hơn nữa, nếu cắt ở tất cả các chỗ hợp lệ $c_1 < c_2 < \ldots < c_K = N$ thì đoạn thứ $t$ chứa đúng các số $c_{t-1}+1 .. c_t$, sắp xếp xong là đúng vị trí. Ngược lại mọi cách chia hợp lệ chỉ dùng các chỗ cắt hợp lệ. Vậy tham lam cắt ở mọi chỗ hợp lệ là tối ưu:
Em nhập hoán vị rồi bấm Tiếp để xem max tiền tố "đuổi theo" chỉ số $i$ và mỗi lần bắt kịp là một nhát cắt:
Cách 1Tính lại max mỗi lần – 60% điểm
Với mỗi $i$, duyệt lại $A_1..A_i$ để tính max rồi so với $i$. Đơn giản, đúng, nhưng tốn $1 + 2 + \ldots + N \approx N^2/2$ phép tính.
Kết quả chạy thật: bản C++ đúng 60/100 test (sai/quá thời gian ở test 61–100), test đúng chạy lâu nhất 0.04s. Bản Python: bản Python đúng 40/100 test (sai/quá thời gian ở test 41–100), test đúng chạy lâu nhất 0.03s. Với $N = 10^5$ cần khoảng $5 \cdot 10^9$ phép so sánh nên quá thời gian.
Cách 2Max tiền tố cập nhật dần – Full điểm
$\max(A_1..A_i)$ $= \max(\max(A_1..A_{i-1}), A_i)$, nên chỉ cần một biến mx cập nhật khi đọc từng số. Không cần lưu mảng.
mx = max(mx, x): sau dòng nàymxlà max của $A_1..A_i$.if (mx == i) k++: đếm một nhát cắt sau vị trí $i$. Ở $i = N$ thì $mx = N$ nên luôn đếm được ít nhất 1.- Đọc $10^6$ số nên nhớ
ios::sync_with_stdio(0); cin.tie(0);.
Kết quả chạy thật: bản C++ đúng 100/100 test, test đúng chạy lâu nhất 0.11s. Bản Python: bản Python đúng 100/100 test, test đúng chạy lâu nhất 0.28s.
Tổng kếtKết quả chạy thật trên bộ test
| Cách | Ngôn ngữ | Đúng | Test đúng lâu nhất | Ghi chú |
|---|---|---|---|---|
| 1. Tính lại max mỗi lần $O(N^2)$ | C++ | 60/100 | 0.04s | Qua $N \le 10^4$ |
| Python | 40/100 | 0.03s | Chậm hơn C++ rất nhiều | |
| 2. Max tiền tố cập nhật dần $O(N)$ | C++ | 100/100 | 0.11s | Full điểm |
| Python | 100/100 | 0.28s | Full điểm |
- Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
- Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
- Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
- Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
- Dễ nản giữa chừng khi không có ai theo sát.
- Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
- Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
- Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
- Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.
Nhận xét
Đăng nhận xét