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

Chữ số (Tin học trẻ toàn quốc 2023 – Vòng khu vực miền Trung – Bảng B, C2)

Chữ số (Tin học trẻ toàn quốc 2023 – Vòng khu vực miền Trung – Bảng B, C2)
Nguồn: Tin học trẻ 2023 Độ khó: Dễ Fibonacci Chu kỳ Pisano Lũy thừa ma trận

Đề bài

Nguồn đề: Hội thi Tin học trẻ toàn quốc 2023 – Vòng khu vực miền Trung – Bảng B, C2 (bài Chữ số). Năm 2023 vòng khu vực có hai bộ đề: miền Bắc và miền Nam dùng chung một đề, miền Trung dùng một đề riêng – bài này thuộc đề miền Trung. Đề 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).

Cho hai số nguyên dương $a, b$. Dãy $fib$ được xây dựng giống dãy Fibonacci nhưng hai số đầu tùy ý:

  • $fib(1) = a$, $fib(2) = b$;
  • $fib(n)$ $= fib(n - 1) + fib(n - 2)$ với mọi $n \ge 3$.

Yêu cầu: cho $a, b, L, R$ ($a, b \le 10^{18}$, $L \le R \le 10^{18}$), tìm chữ số hàng đơn vị của tổng

$S = fib(L) + fib(L + 1)$ $+ \dots + fib(R)$.

Dữ liệu vào:

  • Dòng đầu: số nguyên dương $T$ ($T \le 100$) – số bộ dữ liệu.
  • $T$ dòng sau, mỗi dòng bốn số nguyên dương $a, b, L, R$.

Kết quả: $T$ dòng, mỗi dòng một chữ số – chữ số cuối của $S$ ứng với bộ dữ liệu đó.

Subtask:

  • Subtask 1 (40%): $R \le 10^6$;
  • Subtask 2 (30%): $R - L \le 10^6$;
  • Subtask 3 (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
2
1 1 1 3
1 1 3 3
Ví dụ – output
4
2

Giải thích: với $a = b = 1$ thì $fib(1) = 1$, $fib(2) = 1$, $fib(3) = 2$. Bộ thứ nhất $S = 1 + 1 + 2 = 4$; bộ thứ hai chỉ có một số hạng $S = fib(3) = 2$.

Lưu ý: đề gốc ghi $L < R$ nhưng chính ví dụ lại có bộ $L = R = 3$, nên đề trên CLUE ghi $L \le R$ (các lời giải bên dưới đều xử lý được $L = R$).

Lời mở đầu

Đây là bài "dễ mà dễ sai" của đề miền Trung: số hạng của dãy lớn khủng khiếp (vượt xa long long chỉ sau vài chục số hạng), $R$ tới $10^{18}$ nên không thể cộng từng số. Mấu chốt là đề chỉ hỏi chữ số cuối, tức là chỉ cần làm việc với các số mod 10. Mình đi 3 bước: cộng thẳng (40 điểm) → nhảy nhanh tới $L$ bằng lũy thừa ma trận (70 điểm) → phát hiện dãy mod 10 tuần hoàn chu kỳ 60 (100 điểm).

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ệ điểm subtask của đề: 20 test, mỗi test có tới $T = 100$ bộ dữ liệu (test 1 là ví dụ của đề). Có đủ các trường hợp $a, b = 10^{18}$, $L = 1$, $R = 10^{18}$, $L, R$ rơi đúng bội của 60 hoặc lệch 1 so với bội của 60 (để bắt lỗi chỉ số).

NhómTestSố bộ $T$Tổng số bộ$R$ lớn nhất$R - L$ lớn nhất$a, b$ lớn nhất
Subtask 11–82, 5, 10, 1005171.000.000999.9991018
Subtask 29–144, 10050410181.000.0001018
Subtask 315–205, 1005051018≈ 1,00·10181018
Đã 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 (số test đúng / số test của 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 (40%)$R \le 10^6$1–88/83/88/83/88/88/8
Subtask 2 (30%)$R - L \le 10^6$9–140/60/66/61/66/66/6
Subtask 3 (30%)không ràng buộc thêm15–200/60/60/60/66/66/6

Ý tưởngChỉ giữ chữ số cuối và dãy sẽ lặp lại

Nhận xét 1. Chữ số cuối của một tổng chỉ phụ thuộc vào chữ số cuối của các số hạng: $(x + y) \bmod 10$ $= \big((x \bmod 10) + (y \bmod 10)\big) \bmod 10$. Vậy thay vì $fib(n)$ khổng lồ, ta chỉ cần $f(n) = fib(n) \bmod 10$, và $f(n) = (f(n-1) + f(n-2)) \bmod 10$ – các số luôn từ 0 đến 9.

Nhận xét 2 (tuần hoàn). Số hạng tiếp theo chỉ phụ thuộc vào cặp hai số hạng liền trước $(f(n-1), f(n))$. Cặp này chỉ có $10 \times 10 = 100$ khả năng, nên chắc chắn có lúc cặp bị lặp lại, và từ đó dãy lặp lại y hệt. Hơn nữa dãy còn "chạy lùi" được ($f(n-2) = f(n) - f(n-1)$) nên phần lặp bắt đầu ngay từ đầu dãy. Người ta đã biết dãy Fibonacci mod 10 có chu kỳ 60 (gọi là chu kỳ Pisano), và điều này đúng với mọi cặp $a, b$ ban đầu vì $fib(n)$ luôn là tổ hợp $x \cdot a + y \cdot b$ của hai dãy Fibonacci chuẩn.

Nhận xét 3. Gọi $P(n) = f(1) + f(2) + \dots + f(n)$ (mod 10). Khi đó đáp án là $(P(R) - P(L - 1)) \bmod 10$, và nhờ chu kỳ 60:

$P(n) = \lfloor n / 60 \rfloor \cdot P(60)$ $+ P(n \bmod 60) \pmod{10}$.

Em nhập $a, b, L, R$ rồi bấm Tiếp để xem dãy $f$ được lập, chu kỳ 60 hiện ra, và cách tính đáp án chỉ bằng vài phép tra bảng:

Ví dụ nhanh:

Một điều thú vị em sẽ thấy trong widget: $P(60)$ luôn bằng 0 (tổng một chu kỳ chia hết cho 10). Lý do: với dãy kiểu Fibonacci có $fib(1) + \dots + fib(n)$ $= fib(n + 2) - fib(2)$, nên $P(60)$ $= f(62) - f(2)$ $= f(2) - f(2)$ $= 0$. Dù vậy lời giải cuối mình vẫn viết đủ công thức cho dễ hiểu.

Cách 1Cộng thẳng từ 1 tới R (Subtask 1)

Đi lần lượt $i = 1, 2, \dots, R$, giữ hai biến x = f(i), y = f(i + 1) (đã mod 10). Gặp $i \ge L$ thì cộng x vào tổng. Chú ý a % 10 ngay từ đầu vì $a, b$ tới $10^{18}$.

C++▶ Chạy tay từng dòng codeCách 1: trượt cặp (x, y) = (f(i), f(i+1)) từ 1 tới R, cộng các số hạng từ L.
Dữ liệu mẫu:
  • Không cần mảng: mỗi bước chỉ "trượt" cặp $(x, y) \to (y, x + y)$.
  • Mọi phép cộng đều % 10 nên không bao giờ tràn số.
Không gian$O(1)$
Thời gian$O(T \cdot R)$

Kết quả chạy thật: Bản C++ đúng 8/20 test, quá thời gian test 9–20 (các test đúng chạy lâu nhất 0.189s) → 40 điểm. Bản Python đúng 3/20 test, quá thời gian test 4–20 (các test đúng chạy lâu nhất 0.212s) – Python cộng tới $10^8$ lần (100 bộ × $R = 10^6$) thì quá chậm, chỉ qua được 3 test nhỏ.

Cách 2Nhảy tới L bằng lũy thừa ma trận (Subtask 1, 2)

Ở subtask 2, $L$ rất lớn nhưng đoạn $[L, R]$ ngắn ($\le 10^6$ số). Phần tốn thời gian của cách 1 là "đi bộ" từ 1 tới $L$. Ta "nhảy" thẳng tới $L$ bằng công thức ma trận quen thuộc của Fibonacci:

$\begin{pmatrix} fib(i+1) \\ fib(i) \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^{i-1} \begin{pmatrix} fib(2) \\ fib(1) \end{pmatrix}$

Lũy thừa ma trận $2 \times 2$ tính bằng lũy thừa nhanh trong $O(\log L)$ phép nhân (mọi phần tử mod 10). Có $f(L)$ và $f(L+1)$ rồi thì cộng tiếp từ $L$ tới $R$ như cách 1.

C++▶ Chạy tay từng dòng codeCách 2: lũy thừa nhanh ma trận [[1,1],[1,0]] (mod 10) để có ngay f(L), f(L+1), rồi cộng từ L tới R.
Dữ liệu mẫu:
  • luyThua(M, p): nếu bit thấp nhất của p là 1 thì nhân kết quả với M; sau đó bình phương M và dịch p sang phải.
  • Từ $P = M^{L-1}$: $f(L+1)$ $= P_{00} \cdot f(2) + P_{01} \cdot f(1)$ và $f(L) = P_{10} \cdot f(2) + P_{11} \cdot f(1)$.
Không gian$O(1)$
Thời gian$O(T \cdot (\log L + R - L))$

Kết quả chạy thật: Bản C++ đúng 14/20 test, quá thời gian test 15–20 (các test đúng chạy lâu nhất 0.243s) → 70 điểm. Subtask 3 có $R - L$ gần $10^{18}$ nên vòng cộng vẫn quá chậm. Bản Python đúng 4/20 test, quá thời gian test 4–8, 10–20 (các test đúng chạy lâu nhất 0.143s).

Cách 3Chu kỳ 60 – tổng tiền tố (full)

Làm đúng như phần ý tưởng: lập $f(1..60)$ và tổng tiền tố pre[0..60] (mod 10), rồi $P(n)$ = (số chu kỳ đầy đủ) × pre[60] + pre[n % 60].

C++▶ Chạy tay từng dòng codeCách 3: lập f[1..60] và tổng tiền tố pre (mod 10), mỗi P(n) chỉ là một phép tra bảng nhờ chu kỳ 60.
Dữ liệu mẫu:
  1. du % 10: số chu kỳ $\lfloor n / 60 \rfloor$ có thể tới $1{,}6 \cdot 10^{16}$; lấy mod 10 trước khi nhân cho chắc chắn không tràn.
  2. (P(R) - P(L - 1) + 10) % 10: hiệu có thể âm, cộng thêm 10 để về số không âm (trong C++, % của số âm cho kết quả âm).
  3. Khi $L = 1$ thì P(0) = pre[0] = 0 – đúng.
Không gian$O(60)$
Thời gian$O(T \cdot 60)$

Kết quả chạy thật: Bản C++ đúng 20/20 test, chạy lâu nhất 0.004s. Full điểm. Bản Python đúng 20/20 test, chạy lâu nhất 0.016s – Python cũng nhẹ nhà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. Cộng thẳng từ 1 tới RC++8/200.189s40%
Python3/200.212s0%
2. Nhảy tới L bằng lũy thừa ma trận, cộng L..RC++14/200.243s70%
Python4/200.143s0%
3. Chu kỳ 60 (chu kỳ Pisano)C++20/200.004s100%
Python20/200.016s100%
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, các test quá giờ bị dừng ở khoảng 4 giây). Bài học rút ra: khi đề chỉ hỏi "chữ số cuối" hay "phần dư khi chia cho $m$" của một dãy truy hồi, hãy nghĩ ngay tới tính tuần hoàn – dãy truy hồi bậc 2 mod $m$ chỉ có $m^2$ trạng thái nên chắc chắn lặp. Với mod 10 chu kỳ là 60, mod 100 là 300, mod 1000 là 1500. Nếu không nhớ chu kỳ, em có thể tự tìm bằng cách chạy tới khi cặp $(f(n), f(n+1))$ quay lại $(f(1), f(2))$.
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 😇

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 😇