Gấp Giấy (GAGI)
Đề bài
GAGI – Gấp giấy
Thấy chân bàn bị gập ghềnh nên Nam lấy một tờ giấy lần lượt gấp đôi lại nhiều lần để kê chân bàn. Giả sử tờ giấy có bề dày là $a$ thì sau lần gấp đôi thứ nhất bề dày là $2a$, sau lần gấp đôi thứ 2 là $4a$, lần thứ 3 là $8a$, ... Nếu khoảng gập ghềnh là $b$ thì Nam cần gấp đôi giấy bao nhiêu lần để kê chân bàn ít bị gập ghềnh nhất?
Dữ liệu nhập:
- Là hai số nguyên $a$, $b$ cách nhau một khoảng trắng $(1 \le a \le b \le 1000)$.
Dữ liệu xuất:
- Là số nguyên xác định số lần gấp giấy.
Chú ý: Vì bàn khá nặng không khiêng được nên bề dày giấy gấp không được lớn hơn khoảng gập ghềnh.
Giới hạn: thời gian 1 giây, bộ nhớ 128 MB.
1 422 51Lời mở đầu
Tội nghiệp Nam không có tiền mua bàn mới, các bạn hãy tính toán giúp Nam kê bàn với 😅. Bài này mình được một bạn học viên hỏi nên sẵn tiện mình viết bài giải chi tiết để nhiều bạn được tham khảo luôn. Hy vọng mang đến được giá trị cho các bạn 😇.
Trước khi vào bài, các bạn nhớ lại ngưỡng an toàn mình hay nhắc: với các hệ thống chấm giới hạn 1 giây thì toàn bài chỉ nên lặp khoảng $(3 \div 5) \cdot 10^7$ lần (C/C++), cao lắm là $10^8$ thì hên xui; Python, Java, C# chạy chậm hơn nên ngưỡng còn thấp hơn nữa. Đề cho 2 giây thì nhân 2 ngưỡng lên, 100 ms thì chia 10, cứ thế thôi. Bài này giới hạn 1 giây.
Giải thích ví dụ cho bạn nào chưa hiểu đề: bề dày tờ giấy cứ mỗi lần gấp thì gấp đôi lên, và bề dày đó không được vượt quá khoảng gập ghềnh $b$ (vì bàn nặng quá không nhấc lên nhét giấy dày hơn được). Muốn kê "ít gập ghềnh nhất" thì phải gấp càng nhiều lần càng tốt, miễn vẫn còn $\le b$.
| Ví dụ | Số lần gấp | Bề dày | So với b |
|---|---|---|---|
| a = 1, b = 4 | 0 | 1 | ≤ 4 |
| 1 | 2 | ≤ 4 | |
| 2 | 4 | ≤ 4 ✔ (dừng ở đây) | |
| 3 | 8 | > 4 | |
| a = 2, b = 5 | 0 | 2 | ≤ 5 |
| 1 | 4 | ≤ 5 ✔ (dừng ở đây) | |
| 2 | 8 | > 5 |
Nên đáp án lần lượt là $2$ và $1$.
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:
Cách 1Làm theo đúng luật của đề – O(log(b/a))
Cách làm đơn giản nhất là cứ làm đúng theo luật đề đưa ra: liên tục kiểm tra xem còn gấp đôi được nữa không. Tức là kiểm tra a * 2, nếu vẫn thỏa <= b thì được phép gấp, ta gấp luôn (a *= 2) và tăng biến đếm lên. Cứ thế lặp lại đến khi nào không thể gấp đôi được nữa, tức là a * 2 > b, thì dừng lại và in biến đếm ra.
a * 2 <= b chứ không phải a <= b. Nếu viết while(a <= b) rồi mới gấp thì sẽ đếm dư 1 lần (lần gấp cuối làm giấy dày hơn $b$).Em nhập $a$, $b$ rồi bấm Tiếp để xem tờ giấy dày lên từng lần gấp, so với vạch $b$ màu cam:
Source code để các bạn tham khảo nếu làm theo hướng này:
#include <bits/stdc++.h>
using namespace std;
int main(){
int a, b;
cin >> a >> b;
int cnt = 0;
while(a * 2 <= b){
a *= 2;
cnt++;
}
cout << cnt;
return 0;
}
a, b = map(int, input().split())
cnt = 0
while a * 2 <= b:
a *= 2
cnt += 1
print(cnt)
Bấm Tiếp để chạy tay từng dòng code:
Đánh giá độ phức tạp của cách làm này:
- Không gian: $O(1)$, chỉ vài biến.
- Thời gian: $O(\log_2(b/a))$. Vì sao lại là logarit? Các bạn nhớ lại kiến thức Toán cấp 3: $\log_2(2^k) = k$, ví dụ $\log_2 8 = 3$, $\log_2 4 = 2$. Ta cần tìm số lần gấp $x$ lớn nhất sao cho:
$a \cdot 2^x \le b \iff 2^x \le \dfrac{b}{a} \iff \log_2(2^x) \le \log_2\dfrac{b}{a} \iff x \le \log_2\dfrac{b}{a}$Vậy số lần lặp của vòng
whilechính là phần nguyên của $\log_2(b/a)$. Quy ước khi ghi độ phức tạp thì logarit cơ số mấy cũng chỉ cần viết gọn là $\log$, nên ghi $O(\log(b/a))$ cũng được.
Kiểm tra công thức với vài ví dụ:
- $a = 2, b = 8$: $2 \to 4 \to 8$, nhân thêm thành $16 > 8$ nên gấp $2$ lần. Và $b/a = 4$, $\log_2 4 = 2$ ✔.
- $a = 1, b = 8$: $1 \to 2 \to 4 \to 8$ nên gấp $3$ lần. Và $b/a = 8$, $\log_2 8 = 3$ ✔.
- $a = 3, b = 16$: $3 \to 6 \to 12$, nhân thêm thành $24 > 16$ nên gấp $2$ lần. Và $b/a = 5{,}333\ldots$, $\log_2 5{,}333\ldots \approx 2{,}415$, lấy phần nguyên được $2$ ✔.
Nếu $b/a$ đúng dạng $2^k$ thì đáp án bằng đúng $\log_2(b/a)$; còn không thì đáp án là phần nguyên (nhỏ hơn) của nó.
Cách 2Một dòng với hàm log2
Đã nhìn ra số lần lặp cũng chính là đáp án $\left\lfloor \log_2(b/a) \right\rfloor$ thì ta đâu cần lặp làm gì nữa, chỉ cần 1 dòng lệnh là tính ra ngay. Thư viện toán học của C/C++ có sẵn hàm log2, gọi ra dùng cho khỏe; Python thì có math.log2.
cout << (int)log2((double)b / a);Source code để các bạn tham khảo nếu làm theo hướng này:
#include <bits/stdc++.h>
using namespace std;
int main(){
int a, b;
cin >> a >> b;
cout << (int)log2((double)b / a);
return 0;
}
import math
a, b = map(int, input().split())
print(int(math.log2(b / a)))
log2 trả về số thực. Nếu không ép kiểu thì với $b/a = 8$ nó in ra 3.00000 (hoặc với $b/a = 16/3$ in ra 2.41503) và so với đáp án đúng là 3, 2 thì bị chấm sai. Ngoài ra mình ép (double)b để phép chia là chia số thực – nếu để b / a là chia nguyên thì $16/3$ thành $5$, may mắn vẫn ra đúng đáp án nhưng kiểu mình cẩn thận cho chắc, và mình nghĩ các bạn cũng nên như vậy.Bài này rất dễ, nhưng trong kỳ thi có tính thời gian thì ăn nhau ở chỗ ai code nhanh hơn: dùng vòng lặp như cách 1 thì hơi dở, còn cách 2 chỉ cần 1 dòng là ra kết quả, rất gọn lẹ.
a * 4), ta cần tính $\log_4(b/a)$. C/C++ không có hàm log4, chỉ có log2 và log10 thôi, nên phải "chế" ra bằng công thức đổi cơ số hồi cấp 3:
cout << (int)(log2((double)b / a) / log2(4.0));. Mình luôn ép kiểu số thực khi truyền vào các hàm log vì nguyên mẫu hàm nhận tham số số thực.- 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