Mật Khẩu (PASSWORD) (HSG 12 THPT Lê Quý Đôn Hà Tĩnh 2021-2022)
Đề bài
BÀI 1. MẬT KHẨU (tên file: PASSWORD.*, dữ liệu vào PASSWORD.INP, kết quả PASSWORD.OUT – 7 điểm)
Sau một thời gian không sử dụng Email, Bigw đã quên mật khẩu đăng nhập. Bigw chỉ nhớ rằng mật khẩu là một số nguyên lớn nhất có $N$ chữ số và tổng các chữ số của nó đúng bằng $S$.
Yêu cầu: Hãy giúp Bigw tìm lại mật khẩu.
Dữ liệu vào: đọc từ file văn bản PASSWORD.INP ghi hai số nguyên dương $N$ và $S$ $(1 \le N \le 10^4;\ 0 \le S \le 10^6)$ trên cùng một dòng và cách nhau một dấu cách.
Kết quả ra: Ghi ra file văn bản PASSWORD.OUT một số duy nhất là mật khẩu cần tìm. Nếu không tìm được mật khẩu thì ghi ra số $-1$.
(Khi chạy thử / chấm online thì đọc dữ liệu từ bàn phím và in ra màn hình như bình thường, không dùng file nhé.)
Đề thi học sinh giỏi lớp 12 đợt 3, năm học 2021 – 2022, Trường THPT Lê Quý Đôn – Sở GD&ĐT Hà Tĩnh. Thời gian làm cả đề: 180 phút.
5 12930003 29-1Lời mở đầu
Trong quá trình đi comment dạo mình thấy bài tập này cũng hay nên chia sẻ đề lên đây để anh em cùng xem và thử sức nhé 💪. Bài này là bài 1 trong đề thi HSG lớp 12 của trường THPT Lê Quý Đôn (Hà Tĩnh), rất đơn giản để nhìn ra được tư tưởng, nhưng sẽ có anh em hiện thực code phức tạp – bài viết này mình sẽ chỉ cách code ngắn gọn nhất.
Bạn nào muốn thử nộp bài để hệ thống chấm xem có đúng hay không thì link bài đây nhé:
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 OJGiải thích ví dụ:
- $N = 5$, $S = 12$: cần số có 5 chữ số, tổng chữ số là 12, và lớn nhất. Ví dụ 12900, 39000, 84000... đều có tổng 12 nhưng 93000 là lớn nhất: chữ số đầu đã là 9 (lớn nhất có thể), chữ số thứ hai chỉ còn được $12 - 9 = 3$, các chữ số sau phải là 0.
- $N = 3$, $S = 29$: số có 3 chữ số lớn nhất là 999 nhưng tổng chữ số cũng chỉ đạt tối đa $9 \times 3 = 27 \lt 29$ ⇒ không có số nào thỏa mãn, in $-1$.
Lộ trình lời giải, bấm vào từng phần để nhảy tới:
Cách 1Vét cạn từ số lớn nhất đi xuống – O(10^N · N)
Cách "làm theo đề" ai cũng nghĩ ra: duyệt các số có $N$ chữ số từ lớn nhất ($\underbrace{99\ldots9}_{N}$) giảm dần về nhỏ nhất ($10^{N-1}$), số nào có tổng chữ số bằng $S$ thì đó chính là số lớn nhất cần tìm – in ra và dừng luôn. Duyệt hết mà không gặp thì in $-1$. Riêng $N = 1$ thì số nhỏ nhất có 1 chữ số là 0 (nên với $N = 1, S = 0$ đáp án là 0).
long long chỉ chứa được khoảng 18 chữ số, còn số lượng số phải duyệt là $9 \cdot 10^{N-1}$ – với $N = 10$ đã là 9 tỉ số. Cách này chỉ để hiểu đề và làm code đối chiếu (mình dùng nó để kiểm tra cách 2), đi thi thì chỉ ăn được vài test nhỏ thôi nha.Source code để các bạn tham khảo nếu làm theo hướng này:
#include <iostream>
using namespace std;
int TongChuSo(long long x) {
int tong = 0;
while (x != 0) {
tong += x % 10;
x /= 10;
}
return tong;
}
int main() {
int n, s;
cin >> n >> s;
long long end = 1;
for (int i = 1; i <= n; i++) {
end *= 10;
}
long long start = end / 10;
if (n == 1) start = 0;
for (long long x = end - 1; x >= start; x--) {
if (TongChuSo(x) == s) {
cout << x;
return 0;
}
}
cout << -1;
return 0;
}
def tong_chu_so(x):
tong = 0
while x != 0:
tong += x % 10
x //= 10
return tong
n, s = map(int, input().split())
end = 10 ** n
start = 0 if n == 1 else end // 10
ans = -1
for x in range(end - 1, start - 1, -1):
if tong_chu_so(x) == s:
ans = x
break
print(ans)
Đánh giá độ phức tạp của cách làm này:
- Không gian: $O(1)$.
- Thời gian: tối đa $9 \cdot 10^{N-1}$ số, mỗi số tách $N$ chữ số ⇒ $O(10^N \cdot N)$. Chạy tốt khi $N \le 7$.
Cách 2Tham lam từng chữ số từ trái qua phải – O(N)
Giả sử số có độ dài $N$ thì ta hiểu rằng sẽ có $N$ chữ số được đánh từ trái qua phải. Để số kết quả là số lớn nhất thì hãy cho từng chữ số là chữ số lớn nhất có thể, tính từ trái qua phải. Bởi vì hai số cùng $N$ chữ số thì số nào có chữ số đầu tiên (khác nhau) lớn hơn sẽ lớn hơn – nên ưu tiên chữ số bên trái lớn trước đã.
Như ví dụ mẫu $N = 5$ và tổng là 12 thì ta hiểu số sẽ có dạng $\overline{abcde}$:
- $a$ sẽ nhận số 9 là chữ số lớn nhất có thể, lúc này tổng còn $12 - 9 = 3$. Vậy số giờ có dạng $\overline{9bcde}$.
- Tiếp đến xét chữ số $b$: lúc này tổng chỉ còn 3 nên chữ số lớn nhất có thể của $b$ lúc này chỉ là 3. Vậy số có dạng $\overline{93cde}$.
- Vì tổng còn 0 nên các chữ số $c, d, e$ còn lại lần lượt sẽ là 0. Kết quả ta có: 93000.
Còn như ví dụ tiếp theo $N = 3$, tổng $= 29$ thì rõ ràng ta thấy số có 3 chữ số lớn nhất khi các chữ số đều là 9, thì tổng các chữ số tối đa chỉ là $9 \times 3 = 27$ thôi, vì thế tổng 29 đã vượt quá mức tối đa có thể nên kết quả in ra $-1$ không thể có số nào thỏa mãn.
Minh hoạ điền từng chữ số
Các bạn nhập $N$, $S$ rồi bấm Tiếp để xem từng chữ số được điền (xanh: chữ số 9, cam: phần dư $S \bmod 9$, xám: chữ số 0):
Tư tưởng dễ dàng nhìn ra như vậy nhưng sẽ có anh em hiện thực code phức tạp, anh em lưu ý mình làm cách nào cũng được miễn sau cùng mình xuất ra nội dung kết quả đúng như đề bài cần nha. Vì vậy nên chúng ta chẳng cần phải tạo biến lưu trữ kết quả rồi tính toán cập nhật trên biến đó làm chi cả, mình xuất trực tiếp ra đáp án cần luôn:
- Nếu $9N \lt S$ ⇒ in $-1$.
- Ngược lại: in
sz = S / 9chữ số 9; nếuS % 9 != 0thì in thêm chữ sốS % 9và tăngszlên 1 (đã dùng thêm 1 ô); cuối cùng inN - szchữ số 0 cho đủ $N$ chữ số.
(s == 0 && n > 1) cho chắc ăn nhé.Source code để các bạn tham khảo nếu làm theo hướng này:
#include <iostream>
using namespace std;
int main() {
int n, s;
cin >> n >> s;
if (9 * n < s || (s == 0 && n > 1)) {
cout << -1;
}
else {
int sz = s / 9;
for (int i = 1; i <= sz; i++) {
cout << 9;
}
if (s % 9 != 0) {
cout << s % 9;
sz++;
}
for (int i = n - sz; i >= 1; i--) {
cout << 0;
}
}
return 0;
}
n, s = map(int, input().split())
if 9 * n < s or (s == 0 and n > 1):
print(-1)
else:
sz = s // 9
res = '9' * sz
if s % 9 != 0:
res += str(s % 9)
sz += 1
res += '0' * (n - sz)
print(res)
Khung chạy tay (để dễ theo dõi, giới hạn $N \le 30$):
Đánh giá độ phức tạp của cách làm này:
- Không gian: không lưu gì cả, in thẳng ra – $O(1)$.
- Thời gian: in đúng $N$ chữ số ⇒ $O(N)$, với $N = 10^4$ là tức thì.
9 * n tối đa $9 \cdot 10^4$ nên dùng int thoải mái, không lo tràn số.- 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