Bỏ qua để đến nội dung

Đề số 01 - Ôn thi HSG Tin học THCS

BỘ ĐỀ ÔN THI HỌC SINH GIỎI TIN HỌC THCS Bumbii Academy

ĐỀ SỐ 01 Thời gian làm bài: 150 phút
4 bài, tổng 20 điểm


BàiTên bàiFile chương trìnhFile dữ liệu vàoFile kết quảĐiểm
1Bãi giữ xeXEDAP.*XEDAP.INPXEDAP.OUT4
2Con số chủ đạoCHUDAO.*CHUDAO.INPCHUDAO.OUT5
3Mật mã chia hết cho 5SOCHIA5.*SOCHIA5.INPSOCHIA5.OUT5
4Tỉa hàng câyTIACAY.*TIACAY.INPTIACAY.OUT6

Dấu * được thay bằng py hoặc cpp tùy theo ngôn ngữ lập trình sử dụng.

Bãi giữ xe của trường chỉ nhận hai loại xe: xe đạp (2 bánh) và xe đạp ba bánh (3 bánh) của các em mẫu giáo. Cuối buổi, bác bảo vệ đếm được tất cả có m chiếc xe và n bánh xe.

Yêu cầu: Hãy cho biết trong bãi có bao nhiêu chiếc xe đạp và bao nhiêu chiếc xe ba bánh.

Dữ liệu vào: Từ file văn bản XEDAP.INP gồm một dòng chứa hai số nguyên dương m, n.

Kết quả: Ghi ra file văn bản XEDAP.OUT:

  • Nếu tìm được, ghi hai số nguyên lần lượt là số xe đạp và số xe ba bánh, cách nhau một dấu cách (có thể có loại xe nào đó bằng 0).
  • Nếu bác bảo vệ đã đếm nhầm (không có cách nào thỏa mãn), ghi -1.

Ví dụ:

XEDAP.INPXEDAP.OUTGiải thích
5 123 23 xe đạp và 2 xe ba bánh: 3 + 2 = 5 xe, 3 × 2 + 2 × 3 = 12 bánh.
4 7-14 xe có ít nhất 8 bánh, không thể chỉ có 7 bánh.

Ràng buộc:

  • Có 60% số test với 1 ≤ m, n ≤ 105.
  • Có 40% số test với 1 ≤ m, n ≤ 1018.

Con số chủ đạo của một số nguyên dương được tính như sau: cộng các chữ số của số đó, nếu kết quả có nhiều hơn một chữ số thì lại cộng các chữ số của kết quả, cứ thế cho đến khi chỉ còn một chữ số. Chữ số cuối cùng chính là con số chủ đạo.

Ví dụ: 59 → 5 + 9 = 14 → 1 + 4 = 5, vậy con số chủ đạo của 59 là 5.

Yêu cầu: Cho dãy n số nguyên dương a1, a2, …, an. Hãy tìm con số chủ đạo xuất hiện nhiều lần nhất trong dãy (nếu có nhiều con số như vậy thì chọn con số nhỏ nhất), và liệt kê các số trong dãy có con số chủ đạo đó.

Dữ liệu vào: Từ file văn bản CHUDAO.INP gồm:

  • Dòng đầu tiên chứa số nguyên dương n.
  • Dòng thứ hai chứa n số nguyên dương a1, a2, …, an, các số cách nhau một dấu cách.

Kết quả: Ghi ra file văn bản CHUDAO.OUT gồm:

  • Dòng đầu tiên ghi hai số: con số chủ đạo tìm được và số lượng số trong dãy có con số chủ đạo đó.
  • Dòng thứ hai ghi các số trong dãy có con số chủ đạo đó, theo đúng thứ tự trong dãy.

Ví dụ:

CHUDAO.INPCHUDAO.OUTGiải thích
5
23 7 59 26 50
5 3
23 59 50
Con số chủ đạo của các số lần lượt là 5, 7, 5, 8, 5. Số 5 xuất hiện 3 lần.

Ràng buộc:

  • Có 50% số test với n ≤ 1000, ai ≤ 109.
  • Có 50% số test với n ≤ 105, ai ≤ 1018.

Bạn Nam nhận được một mảnh giấy ghi xâu kí tự S chỉ gồm chữ cái in thường (a…z) và chữ số (0…9). Trong S, mỗi đoạn gồm các chữ số đứng liền nhau (không thể kéo dài thêm về hai phía) được gọi là một số của xâu. Ví dụ, xâu hsg8ngay21thang4nam2023 có các số 8, 21, 4, 2023.

Để mở khóa két bí mật, Nam cần trả lời hai câu hỏi:

  1. Tổng T của tất cả các số trong xâu S là bao nhiêu?
  2. Viết liên tiếp tất cả các chữ số của S theo đúng thứ tự, ta được dãy chữ số a. Xóa đi một số chữ số của a (giữ nguyên thứ tự các chữ số còn lại) để được số b lớn nhất chia hết cho 5. Số b là bao nhiêu?

Dữ liệu vào: Từ file văn bản SOCHIA5.INP gồm một dòng chứa xâu S. Mỗi số trong S có không quá 9 chữ số.

Kết quả: Ghi ra file văn bản SOCHIA5.OUT gồm hai dòng:

  • Dòng thứ nhất ghi tổng T (nếu S không có chữ số nào thì T = 0).
  • Dòng thứ hai ghi số b (không ghi các chữ số 0 vô nghĩa ở đầu). Nếu không có cách nào để được một số chia hết cho 5 thì ghi -1.

Ví dụ:

SOCHIA5.INPSOCHIA5.OUTGiải thích
hsg8ngay21thang4nam20232056
821420
T = 8 + 21 + 4 + 2023 = 2056. Dãy chữ số là 82142023; giữ lại 821420 (xóa 2, 3 ở cuối).
lop9a2x718
-1
T = 9 + 2 + 7 = 18. Dãy chữ số 927 không có chữ số 0 hay 5 nên không tạo được số chia hết cho 5.

Ràng buộc: Gọi L là độ dài xâu S.

  • Có 30% số test với L ≤ 16.
  • Có 30% số test với L ≤ 1000.
  • Có 40% số test với L ≤ 106.

Dọc con đường vào trường có n cây xanh, cây thứ i cao hi mét. Trước mùa mưa bão, nhà trường thuê một xe tỉa cành. Xe có một lưỡi cắt nằm ngang được đặt ở độ cao H mét (H là số nguyên không âm): khi xe chạy dọc hàng cây, mọi phần cây cao hơn H đều bị cắt đi, cây nào không cao hơn H thì giữ nguyên. Như vậy cây cao hi > H cho ra hi − H mét cành.

Nhà trường cần thu được ít nhất k mét cành để làm phân bón, nhưng muốn các cây được giữ lại càng cao càng tốt.

Yêu cầu: Tìm độ cao H lớn nhất để tổng số mét cành cắt được không nhỏ hơn k.

Dữ liệu vào: Từ file văn bản TIACAY.INP gồm:

  • Dòng đầu tiên chứa hai số nguyên dương n và k.
  • Dòng thứ hai chứa n số nguyên dương h1, h2, …, hn.

Dữ liệu bảo đảm k ≤ h1 + h2 + … + hn.

Kết quả: Ghi ra file văn bản TIACAY.OUT một số nguyên duy nhất là độ cao H tìm được.

Ví dụ:

TIACAY.INPTIACAY.OUTGiải thích
4 7
20 15 10 17
15Đặt H = 15 cắt được (20 − 15) + (17 − 15) = 7 mét. Nếu H = 16 chỉ cắt được 4 + 1 = 5 mét, không đủ.

Ràng buộc:

  • Có 40% số test với n ≤ 1000, hi ≤ 1000.
  • Có 60% số test với n ≤ 105, hi ≤ 109.

Gọi x là số xe đạp, y là số xe ba bánh. Ta có hệ:

  • x + y = m
  • 2x + 3y = n

Lấy phương trình thứ hai trừ 2 lần phương trình thứ nhất: y = n − 2m, suy ra x = m − y = 3m − n. Bài toán có nghiệm khi và chỉ khi x ≥ 0 và y ≥ 0.

  • Cách vét cạn (đạt 60%): thử mọi x từ 0 đến m, tính y = m − x rồi kiểm tra 2x + 3y = n. Độ phức tạp O(m), không chạy kịp khi m tới 1018.
  • Cách dùng công thức (đạt 100%): O(1).

Lưu ý với C++: m, n tới 1018 nên phải dùng long long; giá trị 2m tới 2 × 1018 vẫn nằm trong giới hạn của long long (khoảng 9,2 × 1018).

with open("XEDAP.INP") as f:
m, n = map(int, f.read().split())
# x xe đạp, y xe ba bánh: x + y = m, 2x + 3y = n
y = n - 2 * m
x = m - y
with open("XEDAP.OUT", "w") as f:
if x >= 0 and y >= 0:
f.write(f"{x} {y}\n")
else:
f.write("-1\n")

Cách làm trực tiếp là cộng chữ số lặp lại cho đến khi còn một chữ số. Cách này đủ nhanh cho cả hai subtask, vì một số tới 1018 chỉ có 19 chữ số.

Có một tính chất đẹp hơn: một số và tổng các chữ số của nó có cùng số dư khi chia cho 9. Vì vậy con số chủ đạo của x > 0 chính là 1 + (x − 1) mod 9 (kết quả nằm trong 1…9).

Sau khi có con số chủ đạo của từng số, dùng mảng dem[1..9] để đếm. Duyệt c từ 1 đến 9 và chỉ cập nhật khi dem[c] lớn hơn hẳn, để khi bằng nhau thì giữ con số nhỏ hơn. Cuối cùng duyệt lại dãy để in các số có con số chủ đạo đó theo đúng thứ tự.

Độ phức tạp O(n).

with open("CHUDAO.INP") as f:
data = f.read().split()
n = int(data[0])
a = data[1:1 + n]
# Con số chủ đạo của x > 0 chính là 1 + (x - 1) mod 9
cs = [1 + (int(x) - 1) % 9 for x in a]
dem = [0] * 10
for c in cs:
dem[c] += 1
best = 1
for c in range(2, 10):
if dem[c] > dem[best]:
best = c
with open("CHUDAO.OUT", "w") as f:
f.write(f"{best} {dem[best]}\n")
f.write(" ".join(a[i] for i in range(n) if cs[i] == best) + "\n")

Câu 1. Duyệt xâu, dùng biến so để ghép các chữ số liên tiếp: gặp chữ số thì so = so * 10 + chữ số, gặp chữ cái thì cộng so vào tổng rồi đặt lại so = 0. Mẹo: thêm một kí tự không phải chữ số vào cuối xâu để số cuối cùng cũng được cộng.

Câu 2. Số chia hết cho 5 phải tận cùng bằng 0 hoặc 5. Gọi p là vị trí cuối cùng của chữ số 0 hoặc 5 trong dãy a.

  • Nếu không có vị trí nào như vậy thì đáp án là -1.
  • Ngược lại, đáp án là toàn bộ phần đầu a[0..p], bỏ các chữ số 0 ở đầu.

Vì sao? Mọi số tạo được đều là một dãy con của a[0..p], mà xóa bớt chữ số thì số chỉ có thể nhỏ đi hoặc giữ nguyên. Vậy giữ lại nhiều nhất có thể là tốt nhất.

Bẫy thường gặp:

  • Phần đầu toàn chữ số 0 (ví dụ a = 0071): đáp án là 0, không phải xâu rỗng.
  • Dãy a rất dài (tới 106 chữ số): không đổi a ra số nguyên, mà in ra dạng xâu.

Độ phức tạp O(L).

with open("SOCHIA5.INP") as f:
s = f.read().strip()
# Câu 1: tổng các số (dãy chữ số liên tiếp dài nhất) trong S
tong = 0
so = 0
for ch in s + "#":
if ch.isdigit():
so = so * 10 + int(ch)
else:
tong += so
so = 0
# Câu 2: a là dãy tất cả chữ số của S. Kết quả phải tận cùng bằng 0 hoặc 5.
# Lấy trọn phần đầu của a đến vị trí 0/5 cuối cùng là tốt nhất, vì xóa thêm
# chữ số nào cũng làm số nhỏ đi.
a = "".join(ch for ch in s if ch.isdigit())
p = max(a.rfind("0"), a.rfind("5"))
if p == -1:
b = "-1"
else:
b = a[:p + 1].lstrip("0") or "0"
with open("SOCHIA5.OUT", "w") as f:
f.write(f"{tong}\n{b}\n")

Gọi f(H) là tổng số mét cành cắt được khi đặt lưỡi cắt ở độ cao H. Khi H tăng thì f(H) giảm (không tăng). Ta cần H lớn nhất có f(H) ≥ k, đây là dạng điển hình của chặt nhị phân theo kết quả.

  • Cách thử lần lượt (đạt 40%): cho H giảm dần từ max(hi) cho đến khi f(H) ≥ k. Mỗi lần tính f mất O(n), tổng O(n × max hi), chỉ chạy kịp khi hi ≤ 1000.
  • Chặt nhị phân (đạt 100%):
    • Giữ hai đầu lo, hi với f(lo) ≥ k và f(hi) < k. Ban đầu lo = 0 (đề bảo đảm f(0) = tổng hi ≥ k) và hi = max(hi) (vì f(max hi) = 0).
    • Mỗi bước thử mid ở giữa, rồi thu hẹp một nửa.
    • Cần khoảng 30 bước, mỗi bước O(n), tổng O(n log max hi).

Lưu ý với C++: tổng f(H) có thể tới 105 × 109 = 1014, phải dùng long long.

with open("TIACAY.INP") as f:
data = list(map(int, f.read().split()))
n, k = data[0], data[1]
h = data[2:2 + n]
def cat_duoc(H):
"""Tổng số mét cành cắt được khi đặt máy tỉa ở độ cao H."""
return sum(x - H for x in h if x > H)
# cat_duoc(H) giảm dần khi H tăng -> chặt nhị phân tìm H lớn nhất có cat_duoc(H) >= k
lo, hi = 0, max(h) # cat_duoc(0) >= k (đề bảo đảm), cat_duoc(max(h)) = 0 < k
while hi - lo > 1:
mid = (lo + hi) // 2
if cat_duoc(mid) >= k:
lo = mid
else:
hi = mid
with open("TIACAY.OUT", "w") as f:
f.write(f"{lo}\n")