Rotate Numbers (NTHU OJ 12934)
Đề bài
12934 – Rotate Numbers (Xoay số)
Cho một số nguyên lớn, hãy xoay toàn bộ số đó đi $180^\circ$ và in ra kết quả. Ví dụ:
Tuy nhiên, đôi khi sau khi xoay, số đó trở nên vô nghĩa, ví dụ:
Ngoài ra, sau khi xoay số, nếu có các chữ số 0 ở đầu thì không in chúng ra, ví dụ:
Dữ liệu vào: Gồm một số nguyên $N$, $N$ có không quá 100 chữ số.
Kết quả: In ra số sau khi xoay $180^\circ$; nếu số sau khi xoay là vô nghĩa thì in ra No. Nhớ in thêm một dấu xuống dòng ở cuối đáp án.
12934 – Rotate Numbers
Description
Given a large integer, please rotate the whole number by $180^\circ$ and print out the result. For example:
However, sometimes after you rotate the number, it becomes meaningless, for example:
Besides, after you rotate the number, if there are some zeros in the front, do not print them out, for example:
Input
The input contains an integer $N$, which $N$ is no more than 100 digits.
Output
Output the 180 degree rotated number, if the rotated number is meaningless, output "No". Please add a newline at the end of your answer.
1680960891Lời mở đầu
Anh em nào đã học xử lý chuỗi ký tự thì thử sức với bài tập Rotate Numbers này xem, có hướng dẫn giải chi tiết của mình để anh em tham khảo 😇. Bài này là một bạn học viên nhắn tin hỏi mình (bài tập trên lớp của bạn ấy, nộp trễ là bị trừ còn 80% điểm 😅). Lúc đó mình vừa đi theo con coi chừng nó vừa tranh thủ code trên… note điện thoại cho bạn ấy luôn 🤣. Đề gốc tiếng Anh, mình đã dịch sang tiếng Việt ở tab đầu, bạn nào muốn đọc bản gốc thì bấm tab English nhé.
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 OJLàm rõ đề bài hơn cho các bạn: hãy tưởng tượng mình viết số lên tờ giấy rồi xoay ngược tờ giấy 180° (lộn đầu xuống). Khi đó thứ tự các chữ số bị đảo ngược, và mỗi chữ số cũng bị lộn ngược. Chỉ có 5 chữ số lộn ngược lại mà vẫn còn là chữ số (vẫn tính số 0 nhé):
(Hàng dưới mỗi ô là chữ số được xoay thật 180° bằng CSS để các bạn nhìn cho rõ.) Còn các số 2, 3, 4, 5, 7 là những số đảo lại không được ⇒ chỉ cần số có một trong các chữ số này là in No. Và khi đảo lại xong, nếu số 0 đứng ở đầu thì ta không in ra thôi.
Ví dụ đề: $16809$ đọc ngược là $9, 0, 8, 6, 1$, lộn từng chữ số: $9 \to 6$, $0 \to 0$, $8 \to 8$, $6 \to 9$, $1 \to 1$ ⇒ 60891. Với $16980$: đọc ngược là $0, 8, 9, 6, 1$ ⇒ $08691$, bỏ số 0 ở đầu ⇒ 8691. Còn $14681$ có chữ số 4 ⇒ No.
Lộ trình lời giải, bấm vào từng phần để nhảy tới:
Cách 1Kiểm tra trước, in sau – 2 lần duyệt chuỗi
Bạn học viên đọc đề đã nhận ra được mấy ý rất đúng: số nhập vào tới 100 chữ số ⇒ không có kiểu số nguyên nào chứa nổi ⇒ dùng kiểu chuỗi (string); rồi tách từng ký tự ra xét các trường hợp: số 0, các số 1/6/8/9, và còn lại. Bài này chỉ là kỹ thuật duyệt chuỗi thôi, khá cơ bản. Hướng làm đầu tiên mình gợi ý cho bạn ấy như sau:
- Xét chuỗi gốc ban đầu, duyệt qua từng ký tự số trong chuỗi. Nếu thấy có sự xuất hiện của các ký tự 2, 3, 4, 5, 7 – chỉ cần có 1 trong số mấy thằng này – thì lập tức xuất
Novà không xử lý tiếp nữa. - Nếu duyệt hết rồi mà không thấy mấy thằng này thì lúc này tiến hành xử lý chuỗi, in ra chuỗi đảo ngược thôi: xét từ cuối chuỗi đi lên. Nếu cuối chuỗi có số 0 thì cứ bỏ qua, xét lên thằng tiếp theo – chỉ bắt đầu từ thằng khác 0 đầu tiên tính từ cuối chuỗi lên, để loại đi các số 0 sẽ đứng đầu sau khi xoay.
- Rồi cứ thế: nếu là 0 thì in ra 0, là 1 in ra 1, là 6 in ra 9, là 8 in ra 8, là 9 in ra 6. Sau cùng ta sẽ có chuỗi đảo ngược.
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(){
string s;
cin >> s;
int n = s.size();
int flag = 1;
int i = 0;
while(i < n){
if(s[i] == '2' || s[i] == '3' || s[i] == '4' || s[i] == '5' || s[i] == '7'){
flag = 0;
break;
}
i++;
}
if(flag == 0)
cout << "No";
else{
for(i = n - 1; i >= 0; i--){
if(s[i] != '0')
break;
}
if(i < 0)
cout << "0"; // N = 0
while(i >= 0){
if(s[i] == '0' || s[i] == '1' || s[i] == '8')
cout << s[i];
else if(s[i] == '6')
cout << "9";
else if(s[i] == '9')
cout << "6";
i--;
}
}
cout << "\n";
return 0;
}
s = input().strip()
n = len(s)
flag = 1
i = 0
while i < n:
if s[i] in "23457":
flag = 0
break
i += 1
if flag == 0:
print("No")
else:
i = n - 1
while i >= 0 and s[i] == '0': # bỏ các số 0 ở cuối chuỗi gốc
i -= 1
res = []
if i < 0:
res.append('0') # N = 0
while i >= 0:
if s[i] in "018":
res.append(s[i])
elif s[i] == '6':
res.append('9')
elif s[i] == '9':
res.append('6')
i -= 1
print("".join(res))
Đánh giá độ phức tạp của cách làm này:
- Không gian: chỉ lưu chuỗi $s$ ⇒ $O(n)$ với $n$ là số chữ số.
- Thời gian: một vòng duyệt từ đầu đến cuối để kiểm tra, cộng một vòng duyệt từ cuối về đầu để in ⇒ $O(n)$, nhưng trường hợp số hợp lệ thì phải đi 2 lượt qua chuỗi.
fgets. Hàm này giữ lại ký tự '\n' ở cuối chuỗi, nên vòng "bỏ các số 0 ở cuối" sẽ dừng ngay tại '\n' mà không bỏ được số 0 nào – mình chạy thử với 16980 (có xuống dòng) thì ra 08691 thay vì 8691. Trong bài mình chuyển sang C++ đọc bằng cin >> s (không dính '\n') nên không còn lỗi này. Bạn nào thích dùng C thì nhớ xoá '\n' ở cuối chuỗi trước khi xử lý nha. Ngoài ra mình thêm một dòng in 0 cho trường hợp $N = 0$ (code gốc in ra dòng trống).Cách 2Một vòng lặp duy nhất – O(n)
Cách 1 chưa được tối ưu về mặt trình bày lẫn hiệu suất là bởi vì nó tách biệt 2 quy trình: kiểm tra xem chuỗi có hợp lệ hay không là 1 vòng lặp, rồi sau đó nếu hợp lệ lại tiếp tục duyệt 1 vòng lặp nữa để đi tìm ra chuỗi kết quả. Tức là trong trường hợp chuỗi nhập vào hoàn toàn hợp lệ thì code đó sẽ chạy 2 lần chuỗi: một lần từ đầu đến cuối để kiểm tra, và một lần từ cuối về đầu để in kết quả.
Mình có thể tối ưu lại chỉ cần 1 vòng lặp duy nhất vừa để kiểm tra xem chuỗi có hợp lệ hay không, vừa để nếu hợp lệ thì có luôn chuỗi kết quả – mỗi ký tự chỉ xét qua 1 lần duy nhất:
res, rồi đi xét từ cuối chuỗi gốc về đầu, với từng ký tự:
- Gặp ký tự không hợp lệ (2, 3, 4, 5, 7) ⇒ ngay lập tức dừng quá trình xét và kết luận
Noluôn. - Ký tự hợp lệ ⇒ đưa ký tự đã xoay (1→1, 8→8, 6→9, 9→6) vào cuối
res. - Gặp ký tự
'0'thì phải biết có lấy hay không: kiểm trareslúc này đã có dữ liệu hay chưa. Chưa có dữ liệu ⇒ đây là số 0 sẽ đứng đầu ⇒ không đưa vào; đã có dữ liệu rồi ⇒ nhận nó vào.
res. Đơn giản vậy thôi!Ở code này mình còn tận dụng luôn biến j (số ký tự đang có trong res) làm biến cờ hiệu (flag): gặp ký tự không hợp lệ thì gán j = -1, thay vì mất công tạo riêng biến flag như cách trước. Và điều kiện "res đã có dữ liệu chưa" chính là j != 0.
Minh hoạ
Em nhập số $N$ rồi bấm Tiếp để xem vòng lặp đi từ cuối chuỗi về đầu. Ô nhỏ bên dưới mỗi bước cho thấy chữ số đang xét khi bị lộn ngược 180° trông như thế nào:
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(){
string s;
char res[105];
cin >> s;
int j = 0; // j: số ký tự đã có trong res, j = -1 nghĩa là "No"
for(int i = s.size() - 1; i >= 0; i--){
if(s[i] == '2' || s[i] == '3' || s[i] == '4' || s[i] == '5' || s[i] == '7'){
j = -1;
break;
}
else if(s[i] == '0'){
if(j != 0)
res[j++] = s[i];
}
else{
if(s[i] == '1' || s[i] == '8')
res[j++] = s[i];
else if(s[i] == '6')
res[j++] = '9';
else if(s[i] == '9')
res[j++] = '6';
}
}
if(j == -1)
cout << "No";
else if(j == 0)
cout << "0"; // N = 0
else{
res[j] = '\0';
cout << res;
}
cout << "\n";
return 0;
}
s = input().strip()
res = [] # chuỗi đáp án
ok = True
for i in range(len(s) - 1, -1, -1):
c = s[i]
if c in "23457":
ok = False
break
elif c == '0':
if len(res) != 0: # res đã có dữ liệu thì mới nhận số 0
res.append(c)
elif c == '1' or c == '8':
res.append(c)
elif c == '6':
res.append('9')
elif c == '9':
res.append('6')
if not ok:
print("No")
elif len(res) == 0:
print("0") # N = 0
else:
print("".join(res))
Đánh giá độ phức tạp của cách làm này:
- Không gian: chuỗi $s$ và mảng
res⇒ $O(n)$. - Thời gian: mỗi ký tự chỉ xét qua 1 lần duy nhất, gặp ký tự xấu là dừng luôn ⇒ $O(n)$, tối ưu hơn code ban đầu nhiều cả về hiệu suất lẫn cách trình bày.
res chưa có dữ liệu) nên $j$ vẫn bằng 0 – code gốc của mình khi đó in ra dòng trống. Trong bài mình thêm nhánh else if(j == 0) in 0 cho chắc ăn.fgets) mình cũng chạy lại: code một vòng lặp cho kết quả đúng với mọi test trừ $N = 0$; code hai vòng lặp bị dính ký tự '\n' như mình nói ở cách 1 – bản trong bài đã sửa cả hai chỗ.- 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