Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học

Số đặc biệt (Tin học trẻ toàn quốc 2024 – Vòng khu vực – Bảng C2)

Số đặc biệt (Tin học trẻ toàn quốc 2024 – Vòng khu vực – Bảng C2)
Nguồn: Tin học trẻ 2024 Độ khó: Dễ Toán Công thức tổng Căn bậc hai nguyên

Đề bài

Nguồn đề: Hội thi Tin học trẻ toàn quốc 2024 – Vòng khu vực – Bảng C2 (bài Số đặc biệt). Đề vòng khu vực dùng chung cho cả ba miền Bắc, Trung, Nam; bài này nằm trong đề Bảng C2 của miền Trung và miền Nam. Đề bên dưới do mình viết lại bằng lời của mình cho dễ hiểu (giữ đúng nghĩa, đúng số liệu và ví dụ của đề gốc).

Số quá bán của số tự nhiên $X$ là số $a$ nhỏ nhất sao cho $a \times 2 \ge X$. Ví dụ: số quá bán của $4$ là $2$, của $9$ là $5$.

Số cấp dưới của số tự nhiên $X$ là số $b$ lớn nhất sao cho $b \times b < X$. Ví dụ: số cấp dưới của $4$ là $1$, của $5$ là $2$.

Cho hai số tự nhiên $N$ và $M$. Gọi $u$ là tổng các số quá bán của mọi số tự nhiên nhỏ hơn $N$, còn $v$ là tổng các số cấp dưới của mọi số tự nhiên nhỏ hơn $M$.

Yêu cầu: tính $u + v$.

Dữ liệu vào:

  • Dòng thứ nhất: số tự nhiên $N$ ($N \le 10^8$).
  • Dòng thứ hai: số tự nhiên $M$ ($M \le 10^8$).

Kết quả: một số tự nhiên duy nhất là $u + v$.

Subtask:

  • Subtask 1 (30%): $10 \le N, M \le 1000$;
  • Subtask 2 (20%): $N = 1$;
  • Subtask 3 (20%): $M = 1$;
  • Subtask 4 (30%): không có ràng buộc gì thêm.

Giới hạn thời gian: 1 giây. Dữ liệu nhập từ bàn phím, in ra màn hình.

Ví dụ – input
5
6
Ví dụ – output
11

Giải thích: các số tự nhiên nhỏ hơn $N = 5$ là $0, 1, 2, 3, 4$ với số quá bán lần lượt $0, 1, 1, 2, 2$ nên $u = 6$. Các số tự nhiên nhỏ hơn $M = 6$ là $0, 1, \dots, 5$; số $0$ không có số cấp dưới (không có $b$ nào để $b^2 < 0$), các số $1, 2, 3, 4, 5$ có số cấp dưới $0, 1, 1, 1, 2$ nên $v = 5$. Kết quả $6 + 5 = 11$.

Hai chỗ cần để ý
  • Số 0 không có số cấp dưới (không có $b$ nào thoả $b \cdot b < 0$) – ví dụ của đề cho thấy ta tính nó bằng $0$. Số quá bán của $0$ là $0$.
  • Đề gốc ghi nhầm "Dòng thứ nhất" cho cả hai dòng dữ liệu; thực ra $N$ ở dòng thứ nhất, $M$ ở dòng thứ hai. Đáp án lớn nhất cỡ $2{,}5 \cdot 10^{15}$ nên phải dùng long long.

Lời mở đầu

Bài nhẹ nhàng của Bảng C2 vòng khu vực: hai định nghĩa nghe hơi "lạ tai" nhưng thực chất là làm tròn lên của $X/2$ và căn bậc hai nguyên. Mình đi 3 bước: làm đúng định nghĩa (vòng while) → dùng công thức cho từng số $O(N + M)$ → gom nhóm để ra công thức tổng, chạy tức thì.

Dành cho học viên · Ưu tiênNộp bài trên hệ thống CLUE OJ

Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.

Nộp bài trên CLUE OJ

Lộ trình lời giải, bấm vào từng phần để nhảy tới:

Bộ testBộ test có gì?

Bộ test trên CLUE do mình tự sinh theo đúng giới hạn và tỉ lệ subtask của đề: 20 test. Test 1 là ví dụ của đề ($N = 5$, $M = 6$) – không thoả điều kiện $10 \le N, M$ của Subtask 1 nên mình xếp nó vào nhóm "không ràng buộc".

NhómTest$N$ nhỏ nhất – lớn nhất$M$ nhỏ nhất – lớn nhấtĐáp án lớn nhất
Subtask 12–710 – 1.00010 – 1.000270.553
Subtask 28–111 – 11 – 100.000.000666.616.655.001
Subtask 312–152 – 100.000.0001 – 12.500.000.000.000.000
Subtask 41, 16–203 – 100.000.0002 – 100.000.0002.500.666.616.655.001
  • Subtask 2 và 3 có các test biên $N = 1$ / $M = 1$ (một tổng bằng 0) và các test $10^8$ ở tổng còn lại; có cả giá trị $M = 99\,980\,001 = 9999^2$ (đúng một số chính phương) để bắt lỗi lệch 1 ở khúc cuối.
  • Đáp án lớn nhất là $2\,500\,666\,616\,655\,001$ – vượt xa giới hạn int.
Đã nộp thử trên bộ chấm chính thức
Lời giải cuối cùng (cách 3) mình đã nộp lên hệ thống chấm chính thức của Hội thi (tinhoctre.vn) và được 100/100 điểm (AC).

Kết quả chạy thật các cách bên dưới theo từng subtask:

SubtaskĐiều kiệnTestCách 1
C++
Cách 1
Python
Cách 2
C++
Cách 2
Python
Cách 3
C++
Cách 3
Python
Subtask 1 (30%)$10 \le N, M \le 1000$2–76/66/66/66/66/66/6
Subtask 2 (20%)$N = 1$8–111/41/44/41/44/44/4
Subtask 3 (20%)$M = 1$12–154/41/44/41/44/44/4
Subtask 4 (30%)không ràng buộc thêm1, 16–202/62/66/62/66/66/6

Ý tưởngHai định nghĩa thật ra là gì?

Số quá bán. $a$ nhỏ nhất mà $2a \ge X$ chính là $\lceil X/2 \rceil$, trong C++ viết (X + 1) / 2. Các số $X = 1, 2, 3, 4, 5, 6, \dots$ có số quá bán $1, 1, 2, 2, 3, 3, \dots$ – mỗi giá trị lặp đúng 2 lần.

Số cấp dưới. $b$ lớn nhất mà $b^2 < X$, tức là $b^2 \le X - 1$, chính là căn bậc hai nguyên của $X - 1$: $b = \lfloor \sqrt{X - 1} \rfloor$. Giá trị $b$ giữ nguyên trên cả một khúc: với $X$ từ $b^2 + 1$ tới $(b+1)^2$ – khúc này dài $2b + 1$ số.

Nhận xét
Vì các giá trị lặp lại theo từng "khúc" có độ dài biết trước, ta không cần cộng từng số: gom cả khúc rồi nhân. Với $u$ còn ra hẳn công thức đóng; với $v$ chỉ có khoảng $\sqrt{M} \le 10^4$ khúc.

Em nhập $N$, $M$ rồi bấm Tiếp để xem các số được gom thành từng khúc (mỗi màu là một khúc cùng giá trị):

Ví dụ nhanh:

Cách 1Làm đúng định nghĩa (Subtask 1)

Với mỗi $X$, tăng dần $a$ từ 0 tới khi $2a \ge X$; tăng dần $b$ chừng nào $(b+1)^2 < X$ vẫn đúng. Code này bám sát định nghĩa nên rất khó sai – dùng làm "trọng tài" để kiểm tra các cách sau.

C++▶ Chạy tay từng dòng codeCách 1: tính đúng theo định nghĩa bằng hai vòng while.
Dữ liệu mẫu:
  1. Vòng while(a * 2 < X) dừng ở $a$ nhỏ nhất có $2a \ge X$ – đúng định nghĩa số quá bán.
  2. Vòng while((b + 1) * (b + 1) < X) chỉ tăng $b$ khi số kế tiếp vẫn còn bình phương nhỏ hơn $X$, nên khi dừng $b$ là số lớn nhất có $b^2 < X$. Vòng thứ hai bắt đầu từ $X = 1$ vì số $0$ không có số cấp dưới.
Không gian$O(1)$
Thời gian$O(N^2 + M\sqrt{M})$

Kết quả chạy thật: Bản C++ đúng 13/20 test, quá thời gian test 9–11, 16–18, 20 (các test đúng chạy lâu nhất 0.065s). Bản Python đúng 10/20 test, quá thời gian test 9–12, 14–18, 20 (các test đúng chạy lâu nhất 0.051s).

Chuyện lạ: C++ "ăn" luôn Subtask 3
Vòng while(a * 2 < X) a++; bắt đầu từ $a = 0$ quá đơn giản nên trình biên dịch (với -O2) tự thay nó bằng công thức tính thẳng $a$, vì vậy phần tính $u$ chạy như $O(N)$ và bản C++ qua cả các test $M = 1$, $N = 10^8$. Vòng của $v$ thì không được tối ưu như vậy, nên khi $M$ lớn (Subtask 2, 4) vẫn quá thời gian. Bản Python không có "phép màu" này nên chỉ qua Subtask 1 và vài test rất nhỏ. Đừng trông chờ vào trình biên dịch – cứ tự tìm công thức như các cách dưới.

Cách 2Công thức cho từng số – O(N + M)

Số quá bán là (X + 1) / 2, tính $O(1)$. Số cấp dưới thì không giảm khi $X$ tăng, nên giữ $b$ từ số trước và chỉ tăng thêm khi cần – giống hai con trỏ: tổng số lần b++ trên cả vòng lặp chỉ khoảng $\sqrt{M}$.

C++▶ Chạy tay từng dòng codeCách 2: công thức (X+1)/2 và giữ b tăng dần, O(N + M).
Dữ liệu mẫu:
  1. Biến b được khai báo ngoài vòng lặp – đây là khác biệt duy nhất so với cách 1 nhưng biến $O(M\sqrt{M})$ thành $O(M)$.
  2. Không dùng sqrt số thực để tránh sai số làm tròn ở các số chính phương lớn.
Không gian$O(1)$
Thời gian$O(N + M)$

Kết quả chạy thật: Bản C++ đúng 20/20 test, chạy lâu nhất 0.147s. Bản Python đúng 10/20 test, quá thời gian test 9–12, 14–18, 20 (các test đúng chạy lâu nhất 0.024s) – $2 \cdot 10^8$ vòng lặp là quá sức Python trong 1 giây.

Cách 3Gom khúc thành công thức tổng (full)

Tính $u$. Đặt $K = N - 1$ (số lớn nhất được cộng). Các số quá bán của $1..K$ là $1, 1, 2, 2, \dots$:

  • Nếu $K = 2p$: $u = 2(1 + 2 + \dots + p)$ $= p(p + 1)$.
  • Nếu $K = 2p + 1$: thêm một số $p + 1$ nữa, $u = p(p + 1) + (p + 1) = (p + 1)^2$.

Tính $v$. Đặt $Y = X - 1$ thì $v$ là tổng $\lfloor\sqrt{Y}\rfloor$ với $Y = 0, 1, \dots, R$ ($R = M - 2$). Với mỗi $b \ge 1$, các $Y$ có $\lfloor\sqrt{Y}\rfloor = b$ là đoạn $[b^2, (b+1)^2 - 1]$ (khúc cuối bị cắt ở $R$), nên đóng góp $b \times$ (độ dài đoạn). Khúc $b = 0$ đóng góp 0 nên bỏ qua.

C++▶ Chạy tay từng dòng codeCách 3: công thức đóng cho u, đếm theo khúc cho v.
Dữ liệu mẫu:
  1. Khi $N = 1$: $K = 0$, $p = 0$, $u = 0$ ✓. Khi $M = 1$ hoặc $M = 2$: $R < 1$ nên vòng lặp không chạy, $v = 0$ ✓.
  2. Thử với ví dụ: $K = 4 = 2 \cdot 2$ → $u = 2 \cdot 3 = 6$; $R = 4$: $b = 1$ ứng với $Y \in [1, 3]$ đóng góp $3$, $b = 2$ ứng với $Y \in [4, 4]$ đóng góp $2$ → $v = 5$. Tổng $11$ ✓.
  3. Muốn $O(1)$ hoàn toàn cũng được (dùng công thức $\sum b(2b+1)$), nhưng $10^4$ vòng lặp đã là tức thì nên mình giữ cho dễ hiểu.
Không gian$O(1)$
Thời gian$O(\sqrt{M})$

Kết quả chạy thật: Bản C++ đúng 20/20 test, chạy lâu nhất 0.003s. Bản Python đúng 20/20 test, chạy lâu nhất 0.022s. Full điểm cả hai ngôn ngữ.

Tổng kếtKết quả chạy thật trên bộ test

CáchNgôn ngữĐúngLâu nhất
(test đúng)
Điểm theo subtask
1. Đúng định nghĩa (vòng while)C++13/200.065s50%
Python10/200.051s30%
2. Công thức cho từng số – $O(N + M)$C++20/200.147s100%
Python10/200.024s30%
3. Công thức tổng – $O(\sqrt{M})$C++20/200.003s100%
Python20/200.022s100%
Ghi chú của mình: thời gian là thời gian CPU đo trên máy mình (giới hạn 1 giây). Bài học của bài này: khi một đại lượng là hàm "bậc thang" (giữ nguyên trên từng khúc) như $\lceil X/2 \rceil$, $\lfloor\sqrt{X}\rfloor$, $\lfloor N/X \rfloor$..., hãy đếm theo khúc thay vì theo từng số. Và đừng quên đọc kỹ ví dụ để biết trường hợp đặc biệt (số 0) được tính thế nào.
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇

Trồng cây tử đằng

Trồng cây tử đằng
Nguồn: CLUE OJ Độ khó: Dễ Nhập xuất Số học Long long

Đề bài

Con đường từ trường về nhà bạn Huy dài $X$ ki-lô-mét. Huy muốn trồng một hàng cây hoa tử đằng dọc theo con đường này với quy tắc:

  • Mỗi hố trồng cây chiếm một đoạn dài $Y$ mét.
  • Giữa hai hố cây liên tiếp phải chừa một khoảng trống đúng $Z$ mét; giữa đầu đường và hố đầu tiên, cũng như giữa hố cuối cùng và cuối đường, cũng phải chừa ít nhất $Z$ mét.

Hỏi Huy trồng được nhiều nhất bao nhiêu cây? (Nếu con đường quá ngắn thì đáp án là $0$.)

Dữ liệu vào: Một dòng gồm ba số nguyên dương $X, Y, Z$ cách nhau bởi dấu cách.

Kết quả: In ra số cây nhiều nhất có thể trồng.

Giới hạn: $1 \le X, Y, Z \le 2 \cdot 10^9$.

Subtask:

  • Subtask 1 (40% số điểm): $X, Y, Z \le 1000$.
  • Subtask 2 (60% số điểm): không có ràng buộc gì thêm.

Giới hạn thời gian: 1 giây, bộ nhớ: 256 MB. Dữ liệu nhập từ bàn phím và in ra màn hình.

Ví dụ 1 – input
2 500 500
Ví dụ 1 – output
1
Ví dụ 2 – input
1 3 2
Ví dụ 2 – output
199

Giải thích: Ví dụ 1: đường dài $2$ km $= 2000$ m. Trồng 1 cây: $500$ m trống + $500$ m hố + $500$ m trống $= 1500$ m $\le 2000$ m. Trồng 2 cây cần $500 \cdot 2 + 500 \cdot 3 = 2500$ m $> 2000$ m nên chỉ trồng được $1$ cây.

trống 500 mhố 500 mcòn 1000 m: trống ≥ 500 m0L = 2000 m
Ví dụ 1: chừa 500 m, đào 1 hố 500 m, còn 1000 m. Muốn trồng cây thứ 2 phải chừa 500 m, đào hố 500 m rồi chừa thêm 500 m tới cuối đường – cần 1500 m > 1000 m.

Nguồn: phỏng theo bài "TRỒNG CÂY TỬ ĐẰNG" – vinhdinhcoder.net.

Lời mở đầu

Bài này gom đủ ba "cái hố" kinh điển của bài nhập môn: đổi đơn vị (ki-lô-mét và mét), dựng công thức từ mô tả bằng lời, và tràn số (đường dài tới $2 \cdot 10^9$ km $= 2 \cdot 10^{12}$ m). Mình sẽ đi từ cách mô phỏng "trồng từng cây một" (đủ cho subtask 1) tới công thức $O(1)$, và chỉ ra ba cách viết công thức trông rất đúng nhưng vẫn mất điểm vì tràn số – tất cả đều có kết quả chạy thật trên bộ test.

Dành cho học viên · Ưu tiênNộp bài trên hệ thống CLUE OJ

Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.

Nộp bài trên CLUE OJ

Lộ 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ó 20 test, mỗi test là một dòng 3 số. Mình đã kiểm tra: test 1–8 thoả $X, Y, Z \le 1000$ (subtask 1), test 9–20 là subtask 2.

NhómTest$L = 1000X$ lớn nhất (m)Đáp án lớn nhấtSố test
đáp án 0
Số test $1000X$
vượt int
Số test $Y + Z$
vượt int
Subtask 11–81.000.0001.499200
Subtask 29–202.000.000.000.000999.999.999.9991111
  • Có 3 test đáp án bằng 0 (test 3, 4, 11), trong đó có test mà độ dài đường vừa đúng bằng $Z$ (chỉ đủ cho một khoảng trống, không có chỗ cho hố nào). Bộ test không có test nào $1000X < Z$, nhưng lời giải vẫn phải xử lý trường hợp đó (xem phần lời giải).
  • Ở subtask 2, đáp án có thể lên tới gần $10^{12}$ cây (2 test có đáp án vượt $10^9$) – đây là các test làm cách mô phỏng bị quá thời gian.
  • Chỉ 1 test của subtask 2 có $1000X$ còn vừa int; và có test $Y + Z$ cũng vượt int.

Kết quả chạy thật của các cách bên dưới theo từng subtask:

SubtaskTestCách 1
mô phỏng
Bẫy
công thức + int
Bẫy
sửa nửa vời
Lời giải
công thức + long long
Subtask 1 (40%)1–88/88/88/88/8
Subtask 2 (60%)9–2010/121/1211/1212/12

Ý tưởngĐổi ra mét và dựng công thức

Bước 1 – đổi đơn vị: $X$ tính bằng ki-lô-mét, còn $Y, Z$ tính bằng mét. Độ dài con đường là $L = 1000 \cdot X$ mét. Quên bước này thì sai ngay ví dụ 1.

Bước 2 – dựng công thức: trồng $k$ cây thì trên đường có $k$ hố (mỗi hố $Y$ m) và $k + 1$ khoảng trống (một khoảng ở đầu đường, $k - 1$ khoảng giữa các hố, một khoảng ở cuối đường), mỗi khoảng ít nhất $Z$ m. Vậy trồng được $k$ cây khi và chỉ khi

$$k \cdot Y + (k + 1) \cdot Z \le L \iff k \cdot (Y + Z) \le L - Z \iff k \le \frac{L - Z}{Y + Z}.$$

Số cây nhiều nhất là $k = \left\lfloor \dfrac{L - Z}{Y + Z} \right\rfloor$ (phép chia lấy phần nguyên). Nếu $L < Z$ (đường còn không đủ cho một khoảng trống) thì đáp án là $0$.

Thử với ví dụ 2: $L = 1000$, $k = \lfloor (1000 - 2) / (3 + 2) \rfloor$ $= \lfloor 998 / 5 \rfloor$ $= 199$. Đúng bằng đáp án.

Em nhập $X, Y, Z$ rồi bấm Tiếp để xem Huy trồng từng cây (mỗi lần trồng cần thêm $Y + Z$ mét: một hố và một khoảng trống phía sau nó):

Ví dụ nhanh:

Cách 1Mô phỏng trồng từng cây

Cách tự nhiên nhất: biến used là phần đường đã dùng, ban đầu bằng $Z$ (khoảng trống đầu đường). Mỗi lần còn đủ chỗ cho một hố và một khoảng trống phía sau ($Y + Z$ mét) thì trồng thêm một cây.

C++▶ Chạy tay từng dòng codeCách 1: mô phỏng trồng từng cây – mỗi cây dùng thêm Y + Z mét.
Dữ liệu mẫu:
Khi phải lặp nhiều vòng giống nhau (như test đề 2: 199 cây), khung chạy tay tua nhanh các vòng ở giữa.
  1. Điều kiện used + Y + Z <= L đảm bảo sau hố mới vẫn còn khoảng trống $Z$ tới cuối đường (hoặc tới hố sau).
  2. Mình đã dùng long long ở đây để cách này chỉ "thua" vì chậm chứ không vì tràn số.
  3. Số vòng lặp đúng bằng đáp án $k$. Subtask 1 có $L \le 10^6$ nên $k < 10^6$ – chạy tức thì. Subtask 2 có đáp án gần $10^{12}$, máy chỉ chạy được khoảng $10^8$–$10^9$ phép tính mỗi giây.
Không gian$O(1)$
Thời gian$O(k)$ – $k$ là đáp án

Kết quả chạy thật: bản C++ đúng 18/20 test, quá thời gian ở test 9, 14; test đúng chạy lâu nhất 0.719s. Bản Python đúng 17/20 test, quá thời gian ở test 9, 13, 14; test đúng chạy lâu nhất 0.148s (Python chậm hơn nên quá thời gian cả test có đáp án cỡ $10^9$). Subtask 1 đúng hết, subtask 2 mất vì quá thời gian.

BẫyCông thức đúng nhưng tràn số

Có công thức rồi, nhiều bạn viết ngay với int – vì $X, Y, Z \le 2 \cdot 10^9$ vẫn vừa int (tối đa $2\,147\,483\,647$):

C++▶ Chạy tay từng dòng codeBẫy: công thức đúng nhưng X * 1000 tràn int.
Dữ liệu mẫu:

Nhưng $L = 1000X$ lên tới $2 \cdot 10^{12}$ – vượt xa int. Em bấm dữ liệu mẫu 3000000 1 1 trong khung chạy tay: $L = 3 \cdot 10^9$ bị tràn thành số âm, nên L < Z đúng và chương trình in 0 – trong khi đáp án thật là $1\,499\,999\,999$ (con số này vẫn vừa int, chỉ có $L$ bị tràn!).

Kết quả chạy thật: bản C++ đúng 9/20 test, sai ở test 9, 10, 12–20; test đúng chạy lâu nhất 0.004s → mất subtask 2.

Hai cách "sửa" sau đây vẫn chưa đủ:

Sửa lần 1 – đổi L và mẫu số sang long long, còn X, Y, Z vẫn là int:

int X, Y, Z;
cin >> X >> Y >> Z;
long long L = X * 1000;         // BẪY: X * 1000 tính bằng int, tràn TRƯỚC khi gán!
long long d = Y + Z;            // tương tự: Y + Z tràn trước khi gán

Kết quả chạy thật: bản C++ đúng 9/20 test, sai ở test 9, 10, 12–20; test đúng chạy lâu nhất 0.003s. Lý do: X * 1000 có cả hai toán hạng là int nên được tính bằng int và tràn trước khi gán vào long long.

Sửa lần 2 – nhân với 1LL trước để L được tính bằng long long, nhưng quên mẫu số:

long long L = 1LL * X * 1000;   // đã sửa: nhân với 1LL trước nên tính bằng long long
if(L < Z) cout << 0;
else cout << (L - Z) / (Y + Z); // nhưng Y + Z vẫn tính bằng int -> vẫn có thể tràn

Kết quả chạy thật: bản C++ đúng 19/20 test, sai ở test 10; test đúng chạy lâu nhất 0.004s. Đã sửa đúng $L$ nhưng Y + Z (tới $4 \cdot 10^9$) vẫn tính bằng int; ở test 10 nó tràn thành số âm và chương trình in ra số cây âm. Chỉ sai đúng 1 test nhưng vẫn mất trọn 60% của subtask 2!

Cách 2Công thức $O(1)$ với long long

Khai báo $X, Y, Z$ là long long ngay từ lúc đọc thì mọi phép tính (X * 1000, L - Z, Y + Z, phép chia) đều tính bằng long long – không còn gì để tràn:

C++▶ Chạy tay từng dòng codeCách 2: công thức k = (L − Z) / (Y + Z), tính hoàn toàn bằng long long.
Dữ liệu mẫu:
  1. Vì sao cần if(L < Z)? Khi $L < Z$ thì $L - Z$ âm. Phép chia nguyên trong C++ làm tròn về 0: ví dụ $(-5) / 3 = -1$ – sẽ in ra số cây âm. Nên phải xử lý riêng, in 0. (Khi $L - Z < Y + Z$ thì phép chia đã ra 0 đúng như mong muốn.)
  2. Python dùng // là phép chia làm tròn xuống, $(-5) // 3 = -2$, nên mình bọc thêm max(0, ...) cho trường hợp đường quá ngắn. Số nguyên Python không bị tràn.
Không gian$O(1)$
Thời gian$O(1)$

Kết quả chạy thật: Bản C++ đúng 20/20 test; test đúng chạy lâu nhất 0.004s. Bản Python đúng 20/20 test; test đúng chạy lâu nhất 0.022s. Full điểm.

Tổng kếtKết quả chạy thật trên bộ test

CáchNgôn ngữĐúngTest đúng
lâu nhất
Ghi chú
1. Mô phỏng trồng từng câyC++18/200.719squá thời gian test 9, 14 → mất subtask 2
Python17/200.148squá thời gian test 9, 13, 14 → mất subtask 2
Bẫy: công thức, toàn intC++9/200.004ssai test 9, 10, 12–20 → mất subtask 2
Bẫy: long long L = X * 1000 (X là int)C++9/200.003ssai test 9, 10, 12–20 → mất subtask 2
Bẫy: sửa L nhưng quên Y + ZC++19/200.004ssai test 10 → mất subtask 2
2. Công thức, long longC++20/200.004sFull điểm
Python20/200.022sFull điểm
Ghi chú của mình: thời gian là thời gian CPU đo trên máy mình, giới hạn 1 giây (test quá thời gian bị dừng ở khoảng 4 giây). Mô phỏng từng cây ăn trọn subtask 1 nhưng quá thời gian ở các test có đáp án khổng lồ; công thức $k = \lfloor (1000X - Z) / (Y + Z) \rfloor$ chạy tức thì nhưng phải tính hoàn toàn bằng long long: tràn ở $1000X$ mất 11 test, chỉ quên $Y + Z$ cũng mất 1 test – và mất 1 test cũng là mất cả subtask. Thói quen: ước lượng giá trị lớn nhất của từng biểu thức trung gian, không chỉ của đáp án.
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇