2025 - 2026 Lâm Đồng¶
Bài 1: Xâu con¶
Đề bài¶
Anny là một người rất đam mê khám phá, tình cờ Anny có được một tấm bản đồ kho báu.
Trong hành trình tìm kiếm, Anny tìm được cửa kho báu thứ nhất. Trên cửa có ghi một dãy ký tự tiếng Anh in thường liên tiếp nhau. Bản đồ kho báu cho biết mật mã để mở cửa lần này là số lượng xâu con đặc biệt của dãy ký tự trên.
Biết rằng, một xâu con đặc biệt là một dãy ký tự liên tiếp bắt đầu bằng một nguyên âm ('a', 'e', 'i', 'o', 'u') và kết thúc bằng một phụ âm hoặc ngược lại.
Yêu cầu: Hãy giúp Anny đếm số lượng xâu con đặc biệt của xâu ký tự trên cửa.
Đầu vào: XAUCON.INP
Gồm một dòng duy nhất chứa dãy ký tự.
Đầu ra: XAUCON.OUT
Gồm một số nguyên duy nhất là mật mã tìm được.
Ví dụ:
| XAUCON.INP | XAUCON.OUT |
|---|---|
| abco | 4 |
Ràng buộc:
- Có 50% số test ứng với 50% số điểm có chiều dài xâu \(\le 10^3\).
- Có 50% số test ứng với 50% số điểm có chiều dài xâu \(\le 5 \times 10^5\).
Cách giải đề xuất¶
Ý tưởng chính
Với một cặp bất kỳ gồm nguyên âm v và phụ âm c, ta đều vẽ được một và chỉ một đoạn thẳng nối chúng, dù v nằm trước c hay c nằm trước v.
Mỗi cặp v và c như thế đều hình thành một chuỗi con đặc biệt.
Vậy số lượng chuỗi con đặc biệt được tính bằng: tích của số nguyên âm và số phụ âm có trong chuỗi.
Viết chương trình
1. Viết hàm is_vowel() dùng để kiểm ký tự là nguyên âm hay phụ âm.
2. Dùng vòng lặp để đếm số nguyên âm và số phụ âm. Sau đó, tính tích để ra được số chuỗi con đặc biệt.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
Bài 2: Cạm bẫy¶
Đề bài¶
Sau khi mở được cửa thứ nhất của kho báu, Anny cần vượt qua một chướng ngại vật để mở cửa thứ hai. Chướng ngại vật lần này là một ma trận các số nguyên dương không vượt quá \(10^9\) có chứa các cạm bẫy.
Bản đồ kho báu mô tả ma trận gồm \(N\) hàng và \(M\) cột. Cạm bẫy được đặt trên các hàng và các cột có chứa giá trị lớn nhất hoặc nhỏ nhất của ma trận.
Mật mã để mở cửa thứ hai của kho báu là số lượng ô của ma trận không chứa cạm bẫy.
Yêu cầu: Hãy giúp Anny đếm số ô của ma trận không chứa cạm bẫy.
Đầu vào: CAMBAY.INP
- Dòng đầu gồm hai số nguyên dương \(N\) và \(M\) được viết cách nhau một ký tự khoảng trắng.
- \(N\) dòng tiếp theo, mỗi dòng gồm \(M\) số nguyên, các số được viết cách nhau một ký tự khoảng trắng.
Đầu ra: CAMBAY.OUT
Gồm một số duy nhất là mật mã tìm được.
Ví dụ:
| CAMBAY.INP | CAMBAY.OUT |
|---|---|
| 2 3 5 4 3 2 4 2 1 2 3 4 1 2 3 4 4 3 4 2 |
4 |
Ràng buộc:
- Có 40% số test ứng với 40% số điểm có: \(1 \le N, M \le 10\).
- Có 30% số test ứng với 30% số điểm có: \(1 \le N, M \le 100\).
- Có 30% số test ứng với 30% số điểm có: \(1 \le N \times M \le 5 \times 10^6\).
Cách giải đề xuất¶
Ý tưởng chính
1. Gọi matrix là ma trận đầu vào.
-
Ràng buộc \(1 \le N \times M \le 5 \times 10^6\) chỉ cho biết tổng số ô của ma trận, chứ không cho biết rõ giới hạn của riêng \(N\) hoặc \(M\). Điều này có thể tạo ra các tình huống như sau:
- Ma trận vuông với \(N = M \simeq 2300\).
- \(N = 1\) và \(M = 5,000,000\)
- \(N = 5,000,000\) và \(M = 1\)
Nếu ta khai báo dạng
matrix[MAX][MAX]thì phải chọnMAXđủ lớn để bao quát các trường hợp, cụ thể làmatrix[5000000][5000000].Với mỗi
intchiếm 4 byte, mảng trên chiếm đến 91 terabtye: không khả thi. -
Nếu dùng mảng động hai chiều
vector<vector<int>> matrixthì sẽ tốn thời gian tìm địa chỉ bộ nhớ khi nhảy từ hàng này sang hàng khác. Dovector<vector<int>>thực chất là một vector chứa các con trỏ trỏ đến các vector khác nằm rải rác trong bộ nhớ.
Vì vậy, thay vì tổ chức mảng hai chiều, ta nên tổ chức matrix thành mảng động một chiều, cụ thể: vector<int> matrix.
2. Để chuyển đổi từ hai chiều thành một chiều, ta áp dụng công thức tính chỉ số của phần tử là: i = r * m + c, trong đó:
ilà chỉ số của phần tử trongmatrix.rvàclà chỉ số dùng để duyệt theo số hàngnvà số cộtmcủa dữ liệu đầu vào.mlà số cột của dữ liệu đầu vào.
3. Dùng hai mảng một chiều trap_rows và trap_cols để đánh dấu hàng và cột có dính bẫy, trong đó có bẫy là true, không có bãy là false.
4. Số ô không chứa bẫy là tính của số ô false trong trap_rows và số ô false trong trap_cols.
Viết chương trình
1. Đọc dữ liệu đầu vào và chuyển đổi luôn thành mảng một chều matrix. Đồng thời, xác định giá trị nhỏ nhất min_value và lớn nhất max_value.
2. Viết hàm process() dùng để tính kết quả đầu ra như sau:
- Khởi tạo mảng
trap_rowsvàtrap_colschứa toàn giá trịfalse. -
Dùng vòng lặp cho
rchạy đếnn,cchạy đếnm:- Lấy ra phần tử của
matrixmà tương ứng với hàngrvà cộtccủa dữ liệu đầu vào. - Nếu phần tử này bằng giá trị nhỏ nhất hoặc lớn nhất thì đánh dấu
truetrong mảngtrap_rowsvàtrap_cols. - Tính tích các ô
falsetrongtrap_rowsvàtrap_cols.
- Lấy ra phần tử của
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
Bài 3. Mật mã¶
Đề bài¶
Sau khi qua cửa thứ hai, Anny gặp được cánh cửa thứ ba.
Để mở cửa Anny cần có mật mã. Mật mã lần này là chữ số cuối cùng của phép toán a" được ghi trên bản đồ kho báu.
Yêu cầu: Hãy giúp Anny tìm chữ số cuối cùng của phép toán \(a^n\).
Dữ liệu vào: MATMA.INP
Gồm hai số nguyên dương \(a\) và \(n\) được viết cách nhau một ký tự khoảng trắng.
Đầu ra: MATMA.OUT
Gồm một số duy nhất là mật mã tìm được.
Ví dụ:
| MATMA.INP | MATMA.OUT |
|---|---|
| 25 | 2 |
Ràng buộc:
- Có 40% số test ứng với 40% số điểm có: \(1 \le a \le 9; 1 \le n \lt 10^3\)
- Có 30% số test ứng với 30% số điểm có: \(1 \le a \le 10^6; 1 \le n \le 10^6\).
- Có 30% số test ứng với 30% số điểm có: \(1 \le a \le 10^9; 1 \le n \le 10^18\).
Cách giải đề xuất¶
Ý tưởng chính
1. Cách 1: áp dụng cách tính luỹ thừa nhị phân (binary exponentiation).
Thay vì nhân n lần, ta lặp thao tác bình phương cơ số a và nhân nó vào kết quả bất cứ khi nào số mũ hiện hành là lẻ. Cụ thể:
Lặp các thao tác:
-
Nếu số mũ
nlà lẻ thì nhânavào kết quả (biếnresultđược khởi tạo là1).Ngược lại, nếu chẵn thì thôi.
-
Bình phương
a. - Chia đôi số mũ
n.
Vòng lặp dừng khi không thể chia đôi n được nữa.
Ví dụ:
a |
n |
n là chẵn hoặc lẻ |
Kết quả |
|---|---|---|---|
| 2 | 5 | 5 là lẻ | 1 |
| 4 | 2 | 2 là chẵn | 2 |
| 16 | 1 | 1 là lẻ | 2 |
| 256 | 0 | dừng vòng lặp | 32 |
Với \(n = 10^{18}\), vòng lặp trên thực hiện khoảng 60 lần, vì \(2^60 \simeq 10^{18}\).
1. Cách 2: áp dụng tính chất toán học chu kỳ của chữ số tận cùng.
Chữ số tận cùng của \(a^n\) sẽ lặp lại theo chu kỳ tối đa là 4. Cụ thể:
Số cuối của a |
Mũ 1 | Mũ 2 | Mũ 3 | Mũ 4 | Mũ 5 (Lặp lại) | Chu kỳ thực |
|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| 2 | 2 | 4 | 8 | 6 | 2 | 4 |
| 3 | 3 | 9 | 7 | 1 | 3 | 4 |
| 4 | 4 | 6 | 4 | 6 | 4 | 2 |
| 5 | 5 | 5 | 5 | 5 | 5 | 1 |
| 6 | 6 | 6 | 6 | 6 | 6 | 1 |
| 7 | 7 | 9 | 3 | 1 | 7 | 4 |
| 8 | 8 | 4 | 2 | 6 | 8 | 4 |
| 9 | 9 | 1 | 9 | 1 | 9 | 2 |
Theo bảng trên, ta có 3 nhóm:
- Nhóm chu kỳ 1, tức bất biến: 0, 1, 5, 6
- Nhóm chu kỳ 2, tức dao động: 4, 9
- Nhóm chu kỳ 4: 2, 3, 7, 8
Vì 4 là bội chung nhỏ nhất của 1, 2 và 4 nên thay vì mũ n, ta sẽ mũ k với: k = (n - 1) % 4 + 1. Đây là công thức tổng quát nhất.
Viết chương trình
1. Cách 1:
2. Cách 2:
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
Bài 4. Du lịch¶
Đề bài¶
Sau khi tìm được kho báu, Anny sắp xếp một chuyến đi du lịch vòng quanh thế giới.
Có \(N\) điểm du lịch Anny muốn ghé thăm, được đánh số từ \(1\) đến \(N\). Giữa các điểm du lịch có các chuyến bay đi trực tiếp; chi phí để bay từ điểm \(i\) đến điểm \(j\) là \(C_{ij}\) (có thể khác \(C_{ji}\)) và \(1 \le C_{ij} \le 10^9\).
Anny muốn chọn một hành trình du lịch qua \(N\) điểm, mỗi điểm chỉ ghé thăm một lần duy nhất. Với tinh thần tiết kiệm, Anny muốn chọn một hành trình du lịch với tổng chi phí nhỏ nhất.
Yêu cầu: Hãy giúp Anny tìm được hành trình có tổng chi phí nhỏ nhất.
Đầu vào: DULICH.INP
- Dòng đầu ghi số nguyên dương \(N\).
- Dòng thứ \(i\) trong \(N\) dòng tiếp theo, ghi \(N\) số nguyên không âm \(C_{ij}\) là chi phí bay từ điểm \(i\) đến điểm \(j (1 \le j \le n)\).
Đầu ra: DULICH.OUT
Gồm một số nguyên dương duy nhất là tổng chi phí của hành trình nhỏ nhất tìm được.
Ví dụ:
| DULICH.INP | DULICH.OUT |
|---|---|
| 6 0 1 2 1 3 4 5 0 3 2 3 4 4 1 0 2 1 2 4 2 5 0 4 3 2 5 3 5 0 2 5 4 3 3 1 0 |
8 |
Ràng buộc:
- Có 50% số test ứng với 50% số điểm có: \(5 \lt n \le 10\).
- Có 50% số test ứng với 50% số điểm có: \(10 \lt n \le 15\).
Cách giải đề xuất¶
Ý tưởng chính
Sử dụng quy hoạch động trạng thái (bitmask dynamic programming). Cụ thể:
1. Ta dùng một biến nguyên, đặt là mask, biểu diễn các địa điểm đã ghé thăm dưới dạng nhị phân.
Ví dụ:
Với n = 6, mask = 5 nghĩa là đã ghé thăm các điểm 0 và 2, vì biểu diễn nhị phân của 5 là 000101.
2. Gọi d[mask][u] là tổng chi phí nhỏ nhất để đi qua các địa điểm trong mask và kết thúc tại điểm u.
Lưu ý: u được đánh từ 0, thay vì từ 1 như đề bài.
Như vậy, ta có:
-
Trang thái ban đầu:
Với mọi điểm
u, chi phí để ghé thăm chính nó là0:d[1 << u][u] = 0 -
Thay đổi trạng thái:
Đang đứng ở điểm
u, ta thử đi tiếp đếnv:d[mask | (1 << v)][v] = min(d[mask | (1 << v)][v], d[mask][u] + c[u][v])
3. Sau khi đã điền xong các giá trị cho mảng d, thì kết quả output là giá trị nhỏ nhất tính trong số các trạng thái "full mask", tức đã ghé thăm đủ các địa điểm: min(d[(1 << n) - 1][u]), với mọi u.
Hướng tiếp cận trên có độ phức tạp khoảng \(O(2^n \cdot n^2)\). Với \(n = 15\), \(2^15 \cdot 15^2 = 32768 \times 22 \approx 7.3 \times 10^6\) phép tính, dư sức chạy trong 1 giây.
Ngoài ra, với subtask nhỏ (\(n \le 10\)), ta có thể dùng phương pháp quay lui và kỹ thuật nhánh cận.
Độ phức tạp của quay lui là \(O(n!)\). Với \(n \le 10\), \(10! = 1 \times 2 \times \dots \times 10 = \mathbf{3,628,800}\), dư sức chạy trong 1 giây.
Viết chương trình
1. Khai báo các biến liên quan.
2. Khởi tạo mảng d dùng cho quy hoạch động.
3. Thực hiện quy hoạch động.
4. Tìm tổng chi phí nhỏ nhất.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.