Học sinh giỏi 12 Tây Ninh 2024 - 2025 (buổi 1)¶
Bài 1: Bản nhạc hoàn hảo¶
Đề bài¶
Tại thành phố T, nơi nổi tiếng với các bản nhạc đàn ca tài tử, một cuộc thi sáng tác nhạc đã được tổ chức nhằm tìm kiếm bản nhạc hay nhất để trình diễn trong Lễ hội Ánh Trăng.
Các nghệ sĩ trong cuộc thi phải sáng tác những bản nhạc kết hợp từ hai loại nhạc cụ chính là đàn kìm và đàn tranh, bản nhạc là sự kết nối của nhiều đoạn nhạc, trong đó mỗi đoạn chỉ chơi một loại đàn và không có 2 đoạn liên tiếp nào có cùng loại đàn.
Ban giám khảo đã đưa ra tiêu chí đánh giá độ hấp dẫn của mỗi bản nhạc dựa trên độ hấp dẫn của các đoạn nhạc liên tiếp sử dụng hai loại nhạc cụ này.
Có tổng cộng \(N\) đoạn nhạc được đánh số từ \(1\) đến \(N\), đoạn thứ \(i\) được chơi bởi một trong hai loại nhạc cụ trên và có một độ hấp dẫn \(A_i (1 \le i \le N)\) tương ứng, được biểu diễn dưới dạng một số nguyên.
Một bản nhạc hoàn hảo phải thỏa mãn các điều kiện sau:
- Có ít nhất 4 đoạn nhạc liên tiếp.
- Số lượng đoạn nhạc sử dụng đàn kìm và đàn tranh phải bằng nhau.
Độ hấp dẫn của một bản nhạc hoàn hảo được tính bằng tổng độ hấp dẫn của các đoạn nhạc liên tiếp đó.
Yêu cầu: hãy tính độ hấp dẫn lớn nhất của một bản nhạc hoàn hảo có thể được trình diễn.
Dữ liệu: MUSIC.INP
- Dòng đầu chứa số nguyên dương \(N (4 \le N \le 3 \cdot 10^5)\) là số lượng đoạn nhạc.
- Dòng thứ hai chứa \(N\) số nguyên \(A_1, A_2, ..., A_n (|A_i| \le 10^9\) với \(1 \le i\le N)\), mỗi số đại diện cho độ hấp dẫn của một đoạn nhạc (có thể là dương hoặc âm). Hai số liên tiếp được ghi cách nhau bởi một dấu cách.
Kết quả: MUSIC.OUT
Một số nguyên duy nhất là độ hấp dẫn lớn nhất của một bản nhạc hoàn hảo có thể được trình diễn.
Ví dụ:
MUSIC.INP | MUSIC.OUT |
---|---|
7 -3 6 4 -5 -2 7 1 |
11 |
Giải thích: Bản nhạc gồm 6 đoạn có số thứ tự \(2, 3, 4, 5, 6, 7\) sẽ có độ hấp dẫn là \(6 + 4 +(-5) + (-2) + 7 + 1 = 11\).
Ràng buộc:
- Subtask 1 (50%): \(N \le 300\)
- Subtask 2 (30%): \(300 < N \le 5000\)
- Subtask 3 (20%): \(5000 < N \le 3 \cdot 10^5\)
Bài giải đề xuất¶
Ý tưởng chính¶
Sử dụng mảng tổng tiền tố (prefix sum).
Viết chương trình¶
Bước 0:
Khai báo các biến cần thiết.
Bước 1:
Tính mảng tổng tiền tố.
Bước 2:
Gọi ps[right]
(tổng các phần tử trong đoạn A[0..right]
) là tổng đang xét. right
cũng là vị trí kết thúc của mảng con đang xét.
Ta cần tìm vị trí i
sao cho ps[right] - ps[i - 1]
là đạt giá trị lớn nhất (cùng với những điều kiện khác, sẽ đề cập sau).
Với right
là vị trí đang xét, thì ps[right]
là một số không đổi. Cho nên, để ps[right] - ps[i - 1]
đạt giá trị lớn nhất, ps[i - 1]
phải đạt giá trị nhỏ nhất.
Yêu cầu "số lượng đoạn nhạc sử dụng đàn kìm và đàn tranh phải bằng nhau" nghĩa là độ dài right - i + 1
phải là số chẵn. Điều này đồng nghĩa, right
và i - 1
phải cùng chẵn hoặc cùng lẻ.
Do đó, ta dùng hai biến min_ps_even
và min_ps_odd
để theo dõi giá trị ps[i - 1]
nhỏ nhất ứng với trường hợp chẵn và lẻ.
Bước 3:
Duyệt từng phần tử ps[right]
trong đoạn [4..n]
, do yêu cầu có ít nhất 4 đoạn nhạc liên tiếp. Ứng với mỗi right
, ta thực hiện
-
Cập nhật các biến
min_ps_even
vàmin_ps_odd
.Do yêu cầu có ít nhất 4 đoạn nhạc liên tiếp, nên ta có
right - i + 1 >= 4
. Suy rai - 1 <= right - 4
. Đặtk = right - 4
.Như vậy, ta cần cập nhật các biến
min_ps_even
vàmin_ps_odd
dựa trên chỉ sốk = j - 4
. Đây là vị trí bắt đầu xa nhất có thể của mảng con (vị trí kết thúc làright
). -
Tính độ hấp dẫn lớn nhất.
Ta xét
right
là chẵn hoặc lẻ để sử dụngmin_ps_even
vàmin_ps_odd
tương ứng.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
Bài 2: Lễ hội bánh trung thu¶
Trong lễ hội Trung thu tại một làng quê nổi tiếng với nghề làm bánh, có \(N\) loại bánh trung thu đặc biệt được bày bán, mỗi chiếc bánh được đánh số từ \(1\) đến \(N\). Mỗi chiếc bánh trung thu không chỉ có vẻ ngoài đẹp mắt mà còn mang trong mình một giá trị đặc biệt về hương vị, được mô tả bằng giá trị \(A_i\) của bánh thứ \(i\).
Trong lễ hội, người tham gia được phép chọn bánh trong \(K\) lượt chơi. Mỗi lượt, người chơi sẽ chọn một chiếc bánh để thưởng thức và họ sẽ nhận được một điểm thưởng dựa trên giá trị của chiếc bánh đã chọn. Điểm số trong lượt thứ \(i\) của người chơi sẽ là \(i \times\) giá trị của chiếc bánh đã chọn trong lượt đó.
Tuy nhiên, có một điều kiện đặc biệt: người chơi không thể chọn những chiếc bánh quá xa nhau. Cụ thể, nếu ở lượt thứ \(i\) người chơi chọn chiếc bánh có số thứ tự \(p_i\), thì phải bảo đảm \(1 \le p_i - p_{i - 1} \le M\).
Nhiệm vụ của bạn là tính tổng điểm thưởng cao nhất mà người chơi có thể đạt được sau \(K\) lượt chọn bánh.
Dữ liệu: CAKE.INP
- Dòng đầu tiên ghi 3 số nguyên \(N, M, K (M \le N \le 2 \cdot 10^5, K \le min(N, 200))\) lần lượt là số bánh, khoảng cách chọn bánh và số lượt chọn.
- Dòng tiếp theo gồm \(N\) số nguyên dương \(A_1, A_2, ..., A_N (1 \le A_i \le 10^9)\) là các giá trị của mỗi cái bánh.
Kết quả: CAKE.OUT
Một số nguyên duy nhất là tổng điểm thưởng cao nhất mà người chơi có thể đạt được.
Ví dụ:
CAKE.INP | CAKE.OUT |
---|---|
7 6 3 1 10 1 4 6 3 8 |
46 |
7 6 3 3 10 1 4 6 3 8 |
47 |
Giải thích:
- Test 1: Ta chọn các cái bánh ở vị trí 2, 5 và 7. Tổng điểm sẽ là \(1 \cdot 10 + 2 \cdot 6 + 3 \cdot 8 = 46\)
- Test 2: Ta chọn các cái bánh ở vị trí 1, 2 và 7. Tổng điểm sẽ là \(1 \cdot 3 + 2 \cdot 10 + 3 \cdot 8 = 47\)
Ràng buộc:
- Subtask 1 (50%): \(N \le 20\)
- Subtask 2 (30%): \(M \le 100, K \le 20\)
- Subtask 3 (20%): Không có ràng buộc gì thêm.
Bài giải đề xuất¶
Ý tưởng chính¶
Giải bài toán theo hướng quy hoạch động.
Cụ thể, tạo mảng hai chiều D
với D[choice][cake]
là tổng điểm thưởng cao nhất sau choice
lượt chọn, và chiếc bánh được chọn ở lượt thứ choice
(lượt cuối cùng) là bánh có thứ tự cake
.
D[choice][cake]
được tính theo công thức:
D[choice][cake] = D[choice - 1][cake nào đó] + choice * A[cake]
Như vậy, để D[choice][cake]
là cao nhất, ta phải chọn bánh thứ cake nào đó
ở lượt thứ choice - 1
sao cho D[choice - 1][cake nào đó]
là cao nhất.
Mặt khác, theo yêu cầu "nếu ở lượt thứ \(i\) người chơi chọn chiếc bánh có số thứ tự \(p_i\), thì phải bảo đảm \(1 \le p_i – p_{i - 1} \le M\).", ta có:
cake - m <= cake nào đó <= cake - 1
Như vậy, với mỗi vị trí cake
, ta phải duyệt đoạn [cake - m..cake - 1]
.
Để tránh phải duyệt đoạn trên nhiều lần, ta dùng cửa sổ trượt để khi cake
tăng lên, cửa sổ sẽ trượt theo.
Cụ thể, ta sẽ biểu diễn cửa sổ trượt này bằng hàng đợi hai đầu deque
. Hàng đợi này sẽ lưu các chỉ số (vị trí) cake nào đó
sao cho:
-
Các chỉ số
cake nào đó
tăng dần.Tức nếu chỉ số
cake nào đó
quá cũ (cake < cake - m
) thì sẽ bị loại ra khỏi hàng đợi.Nói cách khác, các chỉ số cũng nhất luôn nằm ở đầu hàng đợi (
Q.front()
). -
Các giá trị
D[choice - 1][]
giảm dần.Tức nếu giá trị
D[choice - 1][cake trước đó] <= D[choice - 1][cake mới]
thìcake trước đó
sẽ bị loại ra khỏi hàng đợi.Nói cách khác,
D[][]
có giá trị cao nhất luôn nằm ở đầu hàng đợi (Q.front()
), để khi muốn lấy max, ta chỉ cần lấy phần tử đầu hàng đợi, mà không cần phải duyệt hay so sánh.
Viết chương trình¶
Bước 0:
Khai báo các biến cần thiết.
Bước 1:
Khởi tạo bảng quy hoạch động D
trong phần ý tưởng chính.
Ở lượt chọn đầu tiên, ta có thể chọn bất kỳ chiếc bánh nào.
Bước 2:
Điền bảng quy hoạch D
bằng cách duyệt hàng và cột.
Bước 3:
Duyệt hàng cuối cùng trong D
, lấy ra giá trị cao nhất trong số các giá trị cao nhất.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.
Bài 3: Bảo tồn rừng quốc gia¶
Đề bài¶
Rừng quốc gia CR đang được chính quyền địa phương và các tổ chức bảo tồn môi trường theo dõi chặt chẽ để đảm bảo sự phát triển bền vững. Khu rừng này bắt đầu từ một khu bảo tồn ban đầu, được đánh số 0, số khu bảo tồn n bằng 1, từ đó các khu vực rừng mới dần dần được mở rộng. Mỗi khi một khu vực rừng mới được phát triển, nó sẽ được kết nối với một khu vực rừng hiện có.
Tuy nhiên, các tổ chức bảo tồn cũng cần quản lý rừng một cách khoa học. Đôi khi, một số khu vực rừng sẽ bị tách ra khỏi hệ thống quản lý do thiên tai hoặc can thiệp từ bên ngoài và toàn bộ hệ thống con của nó cũng bị tách ra khỏi hệ thống quản lý. Ngoài ra, họ cần thường xuyên kiểm tra số lượng cây xanh và khu vực còn được bảo vệ trong một khu vực rừng cụ thể. Có thể hình dung hệ thống quản lý rừng giống như đồ thị dạng cây nhưng luôn biến đổi một cách tự nhiên.
Cụ thể, có q truy vấn về việc quản lý rừng, bao gồm các hoạt động sau:
- 1: \(+ u (0 \le u < n)\): Mở rộng thêm một khu vực rừng mới có nhãn \(i = n\), và \(n\) tăng thêm 1. Nối khu vực \(i\) vào khu vực \(u\), và lúc này \(u\) sẽ là cha (tổ tiên trực tiếp) của \(i\).
- 2: \(- u (0 \le u < n)\): Xóa bỏ khu vực \(u\) và hệ thống con của \(u\).
- 3: \(? u (0 \le u < n)\): Báo cáo số lượng khu vực rừng \(u\) và hệ thống con của nó, bao gồm chính khu vực \(u\).
Với mỗi truy vấn loại 3, bạn hãy in ra kết quả của câu hỏi đó.
Dữ liệu: TREE.INP
- Dòng đầu tiên gồm số \(q (1 < q \le 10^5)\) là số lượng truy vấn.
- \(q\) dòng tiếp theo dòng thứ \(i\) lần lượt chứa ký tự \(c\) và số \(u\) cho biết nội dung của truy vấn. Đề bài bảo đảm các điều kiện tại ngay trước thời điểm hỏi của mỗi truy vấn: \(c \in \{+, -, ?\}, 0 \leq u < n\) và \(u\) chưa bị xóa.
Kết quả: TREE.OUT
- Với mỗi lượt truy vấn loại 3, in ra một số nguyên duy nhất là số khu vực rừng đang được bảo vệ trong hệ thống con của khu vực u bao gồm cả chính u.
TREE.INP | TREE.OUT |
---|---|
8 + 0 + 0 + 0 - 1 ? 0 + 2 - 3 ? 2 |
3 2 |
Giải thích:
Với truy vấn loại 3 đầu tiên (? 0), cây gốc 0 chỉ còn 2 nút con là 2 và 3, xuất ra 3 vì đếm cả nút 0.
Ràng buộc:
- Subtask 1 (50%): \(q \le 10^3\)
- Subtask 2 (30%): Không tồn tại truy vấn loại 2
- Subtask 3 (20%): \(q \le 10^5\)
Bài giải đề xuất¶
Ý tưởng chính¶
Lưu và cập nhật kích thước của các cây con bằng cách biến sau:
- Mảng một chiều
parent[]
lưu nút cha của một nút nào đó. - Mảng
children[]
mà mỗi phần tử là một vector để lưu các con trực tiếp của một nút nào đó. - Mảng một chiều
tree_size[]
lưu số lượng nút trong cây con gốc của một nút nào đó.
Viết chương trình¶
Bước 0:
Khai báo các biến cần thiết.
Bước 1:
Viết hàm add()
thể thêm nút con vào nút u
. Hàm này hoạt động như sau:
- Tạo nút mới
new_node
có nhãn làn
. - Đặt nút cha của
new_node
làu
. - Thêm
new_node
vào danh sáchchildren[u]
. - Khởi tạo kích thước bằng 1 cho
new_node
. - Cập nhật kích thước cây con của các nút tổ tiên.
- Tăng
n
lên 1 để chuẩn bị cho những lần thêm nút về sau.
Bước 2:
Viết hàm remove()
để xoá nút u
. Hàm này hoạt động như sau:
- Khai báo các biến tạm để lưu nút cha của
u
và kích thước cây con củau
. -
Xoá nút
u
bằng cách loại bỏu
khỏi danh sáchchildren[p]
(p
là nút cha củau
).Ta không cần xoá các nút con của
u
vì chúng đã bị ngắt liên kết khỏi cây chính. -
Cập nhật kích thước cây con của các nút tổ tiên.
Bước 3:
Viết hàm get_size()
lấy kích thước cây con của nút u
. Hàm này chỉ truy xuất giá trị đã được lưu sẵn là tree_size[u]
.
Bước 4:
Đối với bài này, ta vừa đọc input, vừa xử lý và vừa ghi ra output.
Mã nguồn¶
Code đầy đủ được đặt tại GitHub.