2023 - 2024 Vĩnh Phúc¶
Bài 1: Truy vấn đoạn¶
Đề bài¶
Cho dãy gồm N số nguyên dương đôi một phân biệt \(a_1, a_2, ..., a_n\). Tèo cần trả lời \(Q\) truy vấn, truy vấn thứ \(i\) cho ba số nguyên dương \(l_i, r_i, d_i\) với yêu cầu xác định xem có bao nhiêu số từ vị trí \(l_i\) đến \(r_i\) trong dãy là ước số hoặc bội số của \(d_i\). Bạn hãy lập trình giúp Tèo trả lời các truy vấn nhé!
Dữ liệu:
- Dòng 1: Gồm hai số nguyên \(N, Q (1 \le N, Q \le 10^5)\);
- Dòng 2: Gồm \(N\) số nguyên dương đôi một phân biệt \(a_1, a_2, ..., a_n (1 \le a_i \le 2 \cdot 10^5, 1 \le i \le N)\);
- Tiếp theo là \(Q\) dòng, mỗi dòng mô tả một truy vấn gồm ba số nguyên \(l_i, r_i, d_i \quad (1 \leq l_i \leq r_i \leq N, \ 1 \leq d_i \leq 2 \cdot 10^5, \ 1 \leq i \leq Q)\).
Kết quả:
- Ghi trên một dòng duy nhất gồm \(Q\) số nguyên, số thứ \(i (1 \le i \le Q)\) là câu trả lời cho truy vấn thứ i.
Ví dụ:
| squery.inp | squery.out | Giải thích |
|---|---|---|
| 8 5 12 10 3 18 6 72 28 42 1 8 6 2 7 5 1 5 3 3 7 3 4 4 1 |
6 1 3 1 2 | Trong truy vấn 1: Các số trong đoạn từ vị trí 1 đến 8 là ước hoặc bội của số 6 gồm: 12, 3, 18, 6, 72, 42. |
Ràng buộc:
- 80% số điểm có \(Q = 1\);
- 10% số điểm có \(N, Q \le 1000\);
- 10% số điểm không có ràng buộc bổ sung.
Bài giải đề xuất¶
Để đếm số lượng ước hoặc bội của d, ta duyệt mảng A từ vị trí l đến r và xét từng phần tử trong phạm vi này.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
Bài 2: Triển lãm tranh¶
Đề bài¶
Tèo có N bức tranh được đánh số từ 1 đến \(N\). Bức tranh thứ \(i (1 \le i \le N)\) có kích thước \(S_i\) và giá trị \(V_i\).
Ngoài ra, Tèo có \(M\) khung tranh được đánh số từ 1 đến \(M\). Khung tranh thứ \(j (1 \le j \le M)\) có kích thước \(C_j\) chỉ chứa được một bức tranh có kích thước không vượt quá kích thước của khung tranh.
Tèo cần chọn ra một số bức tranh để trưng bày sao cho thỏa mãn các điều kiện sau:
- Tranh phải được đặt vào trong khung tranh;
- Kích thước của khung tranh bên phải luôn lớn hơn hoặc bằng kích thước bức tranh bên trái;
- Giá trị của bức tranh bên phải luôn lớn hơn hoặc bằng giá trị của bức tranh bên trái;
- Số bức tranh được chọn là nhiều nhất có thể.
Hãy lập trình tính xem Tèo có thể trưng bày nhiều nhất bao nhiêu bức tranh.
Dữ liệu:
- Dòng 1: Gồm hai số nguyên \(N, M (1 \le N, M \le 10^5)\) tương ứng với số bức tranh và số khung tranh;
- Tiếp theo là \(N\) dòng, mỗi dòng gồm hai số nguyên dương \(S_i, V_i (1 \le S_i, V_i \le 10^9, 1 \le i \le N)\) là kích thước và giá trị của bức tranh thứ \(i\);
- Cuối cùng là \(M\) dòng, mỗi dòng gồm một số nguyên \(C_j (1 \le C_j \le 10^9, 1 \le j \le M)\) là kích thước của khung tranh thứ \(j\).
Kết quả:
- In ra một số nguyên là số lượng bức tranh nhiều nhất mà Tèo có thể trưng bày.
Ví dụ:
| picture.inp | picture.out | Giải thích |
|---|---|---|
| 3 4 10 20 5 1 3 5 4 6 10 4 |
2 | Tèo có thể trưng bày nhiều nhất 2 bức tranh, trong đó có một cách trưng bày theo thứ tự như sau: [Tranh 2 đặt trong Khung 2] và [Tranh 1 đặt trong Khung 3] |
Ràng buộc:
- 50% số điểm có \(N, M \le 10\);
- 30% số điểm có \(N, M \le 1000\);
- 20% số điểm không có ràng buộc bổ sung.
Bài giải đề xuất¶
Bước 1: Sắp xếp các bức tranh và khung tranh theo thứ tự nhằm chuẩn bị thực hiện tham lam (greedy).
Bước 2: Thực hiện tham lam như sau:
Duyệt các khung tranh frames[j], lặp thao tác:
Dùng while để duyệt các bức tranh pictures[i] có kích thước nhỏ hơn hoặc bằng khung tranh frames[j].
Nếu bức tranh pictures[i] có giá trị lớn hơn hoặc bằng bức tranh vừa đặt vào khung trước đó (được lưu lại trong biến previous_value) thì:
Đặt tranh vào khung, đồng nghĩa tăng biến đếm.
Ghi nhận giá trị previous_value mới.
Tăng i để chuẩn bị xét tiếp bức tranh tiếp theo.
Ngắt vòng lặp while để duyệt tiếp khung tranh tiếp theo.
Ngược lại, giá trị không lớn hơn, thì chỉ tăng i để xét bức tranh tiếp theo.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
Bài 3: Thám hiểm¶
Đề bài¶
Khu rừng nguyên sinh X nằm trên một trục đường thẳng dài mà ta có thể coi như trục tọa độ. Chỉ có thể di chuyển vào cảnh rừng bằng cách sử dụng trực thăng để bay tới một trong \(n\) điểm tập kết đã được xây dựng sẵn trong khu rừng. Điểm tập kết thứ \(i\) nằm ở tọa độ \(a_i\), chuyến bay tới đó tốn \(b_i\) đơn vị nhiên liệu. Mỗi đội thám hiểm sau khi hạ cánh ở một điểm tập kết sẽ tiếp tục sử dụng phương tiện đường bộ để di chuyển đến điểm thám hiểm. Chi phí di chuyển bằng phương tiện đường bộ là \(c\) đơn vị nhiên liệu cho mỗi đơn vị khoảng cách.
Có \(m\) đội thám hiểm, đội thứ \(j (1 \le j \le m)\) muốn đến thám hiểm ở vị trí tọa độ \(d_j\), họ có thể chọn bay tới một điểm tập kết bất kỳ, sau đó di chuyển tiến hoặc lùi trên bộ để đến đích.
Với mỗi đội thám hiểm, hãy tính xem họ tốn ít nhất bao nhiêu đơn vị nhiên liệu để tới được điểm thám hiểm của mình.
Dữ liệu:
- Dòng 1: chứa ba số nguyên \(n, m, c (1 \le n, m \le 10^5; 1 \le c \le 10^9)\);
- Tiếp theo là \(n\) dòng, mỗi dòng mô tả một điểm tập kết gồm hai số nguyên \(a_i, b_i (0 \le a_i, b_i \le 10^9)\), không có hai điểm tập kết nào cùng tọa độ;
- Cuối cùng là \(m\) dòng, mỗi dòng gồm một số nguyên \(d_j (0 \le d_j \le 10^9)\) mô tả tọa độ cần đến của các đội thám hiểm.
Kết quả:
- Ghi trên \(m\) dòng, dòng thứ \(j (1 \le j \le m)\) ghi tổng chi phí nhỏ nhất để đội thám hiểm thứ \(i\) đến được tọa độ \(d_j\).
Ví dụ:
| expfuel.inp | expfuel.out | Giải thích |
|---|---|---|
| 3 2 1 200 300 300 100 100 250 150 110 |
250 260 |
- Đội 1: bay đến điểm tập kết tọa độ 300, tiêu tốn 100 đơn vị nhiên liệu, sau đó di chuyển tới 150, tốn thêm 150 đơn vị nhiên liệu. - Đội 2: bay đến điểm tập kết tọa độ 100, tiêu tốn 250 đơn vị nhiên liệu, sau đó di chuyển tới 110, tốn thêm 10 đơn vị nhiên liệu. |
Ràng buộc:
- 40% số điểm có \(n, m \le 20\);
- 20% số điểm có \(n, m \le 1000\);
- 40% số điểm không có ràng buộc bổ sung.
Bài giải đề xuất¶
Ta có tổng nhiên liệu tiêu tốn là:
\(fuel = b_i + c \times | a_i - d_j |\)
Trong đó:
- \(b_i\) là nhiên liệu để bay đến điểm tập kết \(a_i\).
- \(c\) là nhiên liệu cho mỗi đơn vị khoảng cách và là hằng số.
- \(c \times | a_i - d_j |\) là nhiên liệu để đi bộ từ điểm tập kết \(a_i\) đến điểm thám hiểm (điểm đích) \(d_j\).
Xét vị trí của điểm tập kết \(a_i\) so với điểm đích \(d_j\), có hai trường hợp:
Trường hợp 1: điểm tập kết nằm bên trái điểm đích
Tổng nhiên liệu là:
\(fuel = b_i + c \times (d_j - a_i) = (b_i - c \times a_i) + c \times d_j\)
Như vậy, với các \(a_i \lt d_j\), ta cần tính \(min(b_i - c \times a_i)\)
Trường hợp 2: điểm tập kết nằm bên phải điểm đích
Tổng nhiên liệu là:
\(fuel = b_i + c \times (a_i - d_j) = (b_i + c \times a_i) - c \times d_j\)
Như vậy, với các \(a_i \gt d_j\), ta cần tính \(min(b_i + c \times a_i)\)
Với phân tích trên, ý tưởng giải quyết bài toán như sau:
- Sắp xếp điểm tập kết
A[]theo toạ độ tăng dần. -
Thực hiện tiền xử lý:
-
Trường hợp 1: tính prefix min từ
0đếnn - 1, lưu vào mảngmin_left[].Nói cách khác, trong
min_left[], càng tiến sang phải giá trị phần tử càng nhỏ dần. -
Trường hợp 2: tính suffix min từ
n - 1về0, lưu vào mảngmin_right[].Nói cách khác, trong
min_right[], càng tiến sang trái giá trị phần tử càng nhỏ dần.
-
-
Ứng với mỗi điểm đích
D[j], thực hiện nhị phân bằng cách dùng hàmupperbound()để tính vị trík:- Các điểm từ
0đếnk - 1đều nằm bên trái củaD[j]. - Các điểm từ
kđếnn - 1đều nằm bên phải củaD[j].
Hình sau minh hoạ các đại lượng liên quan. Lưu ý: số liệu trong hình không phải là dữ liệu đầu vào của đề bài.
Giả sử ta cần tìm điểm tập kết tốt nhất (tổng nhiên liệu tốn ít nhất) từ năm điểm tập kết
A[i].Điểm đích là
D[j] = 250.Hàm
upperbound()sẽ tìm vị trí đầu tiên mà lớn hơn250, đó là300. Vị trí này ứng vớik = 2.- Với các điểm tập kết
A[i]cói < k, ta dùng mảngmin_left[]. - Với các điểm tập kết
A[i]cói >= k, ta dùng mảngmin_right[].
- Các điểm từ
-
Với
ktìm được, tổng nhiên liệu tiêu tốn ít nhất sẽ được tính dựa trên mảngmin_left[k - 1]vàmin_right[k].
Theo đó, ta viết chương trình như sau:
Bước 0:
Khai báo các hằng và biến liên quan.
Bước 1:
Viết hàm tiền xử lý.
Bước 2:
Viết hàm vừa xử lý vừa ghi kết quả vào tập tin.
-
[]: báo hiệu bắt đầu một hàm lambda.(lli dj, const pair<lli, lli>& a): tham số đầu vào của hàm lambda:djlà toạ độ điểm đíchalà một phần tử của mảng điểm tập kếtA[]
return dj < a.first;: dùng để kiểm tra xem toạ độ của điểm tập kếtacó lớn hơn điểm đíchdjhay không.
-
Hàm
bisect_right(positions, dj)trả về vị tríkđể có thể chèndjvàopositionsmà vẫn bảo đảm thứ tự tăng dần củapositions.Hàm này không thực sự chèn, mà chỉ tìm vị trí có thể chèn.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.