Tính S đệ quy
Đề bài
Nhiệm vụ của bài tập này là xây dựng chương trình con đệ quy tính giá trị của $S$ được cho bởi công thức:
$$S(n) = \sqrt{n + \sqrt{n - 1 + \sqrt{n - 2 + \ldots + \sqrt{2 + \sqrt{1}}}}}$$
Dữ liệu vào:
- Gồm số nguyên dương $n$ $(0 \le n \le 10^4)$.
Kết quả:
- In một số duy nhất là đáp án của bài. Đáp án làm tròn 3 chữ số thập phân.
11.00021.73232.175Lời mở đầu
Bài này là một ví dụ rất "đẹp" để tập viết hàm đệ quy: nhìn kỹ công thức sẽ thấy bên trong căn lớn nhất lại chứa đúng biểu thức của $S(n - 1)$. Mình sẽ đi từ ý tưởng tới hai cách cài đặt (đệ quy và vòng lặp), kèm khung chạy tay để các bạn thấy rõ ngăn xếp lời gọi lớn lên rồi co lại như thế nào. Bài thuộc chương Đệ quy cơ bản trên CLUE OJ (nguồn bài: VinhDinhCoder).
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJLộ 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ó 100 test, mỗi test chỉ là một số $n$:
| Test | n | Đáp án nhỏ nhất | Đáp án lớn nhất |
|---|---|---|---|
| 1–20 | 1 … 20 | 1.000 | 4.987 |
| 21–100 | 1.167 … 9.992 | 34.665 | 100.461 |
- Test 1–20 lần lượt là $n = 1, 2, \ldots, 20$ (đúng thứ tự) – dùng để soát công thức.
- Test 21–100 có $n$ ngẫu nhiên từ khoảng 1.100 tới gần $10^4$ – độ sâu đệ quy lên tới gần 10.000 tầng.
- Không có test $n = 0$, nhưng code bên dưới vẫn xử lý được ($S(0) = 0$, in
0.000).
Đề không chia subtask. Mình chia 2 nhóm theo độ lớn của $n$ để xem từng cách ăn được bao nhiêu (kết quả chạy thật, giới hạn 1 giây):
| Nhóm | Điều kiện | Test | Cách 1 (đệ quy) | Cách 2 (vòng lặp) | Lồng ngược (code sai) |
|---|---|---|---|---|---|
| 1 | $n \le 20$ | 1–20 | 20/20 | 20/20 | 1/20 |
| 2 | $1000 < n \le 10^4$ | 21–100 | 80/80 | 80/80 | 0/80 |
Ý tưởngCăn lớn chứa căn nhỏ
Viết công thức cho vài giá trị $n$ nhỏ:
- $S(1) = \sqrt{1} = 1$
- $S(2) = \sqrt{2 + \sqrt{1}}$ $= \sqrt{2 + S(1)}$ $\approx 1{,}732$
- $S(3) = \sqrt{3 + \sqrt{2 + \sqrt{1}}}$ $= \sqrt{3 + S(2)}$ $\approx 2{,}175$
Chọn $S(0) = 0$ cho gọn: khi đó $S(1) = \sqrt{1 + 0} = 1$ đúng như đề, và trường hợp $n = 0$ cũng có đáp án $0$. Chú ý số lớn nhất $n$ nằm ở căn ngoài cùng, số $1$ nằm ở căn trong cùng – nhầm thứ tự này là sai ngay từ test 2.
Khi gọi $S(4)$, máy chưa tính được ngay mà phải gọi $S(3)$, $S(3)$ lại gọi $S(2)$, ... cho tới $S(0)$. Sau đó các giá trị được trả ngược lên. Em nhập $n$ rồi bấm Tiếp để xem ngăn xếp lời gọi:
Cách 1Hàm đệ quy S(n) – $O(n)$
Viết đúng y công thức đệ quy ở trên:
if (n == 0) return 0;là điểm dừng: thiếu dòng này hàm sẽ gọi $S(-1), S(-2), \ldots$ mãi mãi tới khi tràn ngăn xếp.return sqrt(n + S(n - 1));: máy phải tính xong $S(n - 1)$ rồi mới cộng $n$ và lấy căn. Trong lúc chờ, lời gọi $S(n)$ nằm im trong ngăn xếp.printf("%.3f", ...)làm tròn đúng 3 chữ số thập phân như đề yêu cầu.- Độ sâu đệ quy tối đa là $n + 1 \le 10001$ tầng – C++ chạy thoải mái. Bản Python phải gọi
sys.setrecursionlimit(20000)vì giới hạn mặc định chỉ 1000 tầng.
Kết quả chạy thật: Bản C++ đúng 100/100 test; test đúng chạy lâu nhất 0.01s. Bản Python: đúng 100/100 test; test đúng chạy lâu nhất 0.03s.
Cách 2Khử đệ quy bằng vòng lặp – $O(n)$
Nhìn vào widget ở trên: các giá trị được tính theo thứ tự $S(0) \to S(1) \to S(2) \to \ldots \to S(n)$, tức là từ căn trong cùng ra ngoài. Vậy ta chỉ cần một biến s và một vòng lặp, không cần ngăn xếp lời gọi:
slúc đầu bằng $S(0) = 0$; sau vòng lặp thứ $i$,schính là $S(i)$.- Vòng lặp phải chạy từ 1 lên $n$. Nếu ai viết đệ quy kiểu
f(i) = sqrt(i + f(i + 1))bắt đầu từ $i = 1$ thì số 1 lại nằm ở căn ngoài cùng – sai đề. Mình đã thử code "lồng ngược" đó trên bộ test: chỉ đúng 1/100 test (test $n = 1$).
Kết quả chạy thật: Bản C++ đúng 100/100 test; test đúng chạy lâu nhất 0.00s. Bản Python: đúng 100/100 test; test đúng chạy lâu nhất 0.03s.
Tổng kếtKết quả chạy thật trên bộ test
| Cách | Ngôn ngữ | Đúng | Lâu nhất | Ghi chú |
|---|---|---|---|---|
| 1. Hàm đệ quy S(n) | C++ | 100/100 | 0.01s | Full điểm |
| Python | 100/100 | 0.03s | Full (nhớ tăng giới hạn đệ quy) | |
| 2. Vòng lặp từ trong ra ngoài | C++ | 100/100 | 0.00s | Full điểm |
| Python | 100/100 | 0.03s | Full điểm | |
| Lồng ngược (1 ở ngoài cùng) | C++ | 1/100 | 0.00s | Sai công thức, chỉ đúng test n = 1 |
- Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
- Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
- Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
- Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
- Dễ nản giữa chừng khi không có ai theo sát.
- Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
- Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
- Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
- Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.
Nhận xét
Đăng nhận xét