Xin chào (HELLO)
Đề bài
HELLO – Xin chào
Nam là người thích chat với bạn bè trên Internet. Cậu ấy đã lập ra một phòng chat với điều kiện rằng trước khi vào phòng chat, mọi người phải chào hỏi trước.
Một câu chào được định nghĩa rằng, câu chào đó phải là một xâu kí tự, chỉ gồm các chữ cái, không chứa kí tự trắng, sao cho khi xóa đi một số chữ cái, nó sẽ trở thành từ "hello", tất nhiên là sẽ không được phép tráo đổi vị trí các chữ cái, mà chỉ được xóa bớt một số chữ cái.
Ví dụ khi Bình muốn vào phòng chat, Bình gõ "ahhellllloou" thì hệ thống sẽ xem xét xâu này và sẽ tự động loại bỏ các chữ cái để trở thành từ "hello". Như vậy Bình được vào phòng chat.
Nhưng khi Bình gõ "hlelo", hệ thống không thể làm cách nào xóa bớt chữ cái để trở thành từ "hello" được. Như vậy, Bình không được vào phòng chat.
Xác định xem, khi Bình gõ một xâu, xâu đó có được chấp nhận để Bình vào phòng chat hay không?
Dữ liệu nhập: Một dòng chứa xâu chữ cái mà Bình định gõ, với chiều dài từ $1$ đến $100$ chữ cái.
Dữ liệu xuất: Nếu Bình được đồng ý để vào phòng chat, xuất "YES", còn không, xuất "NO" (không chứa dấu ngoặc kép).
Giới hạn: thời gian 1 giây, bộ nhớ 128 MB. Đọc từ bàn phím (standard input), ghi ra màn hình (standard output).
ahhellllloouYEShleloNOhelhcludooYESLời mở đầu
Bài này mình được một bạ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ớ giúp mình chuyện giới hạn thời gian: với các hệ thống chấm bài giới hạn 1 giây, toàn bài chỉ nên lặp cỡ $(3 \div 5) \cdot 10^7$ lần là an toàn, tệ nhất $10^8$ lần (lúc này hên xui). Ngưỡng này là cho C/C++; Python, Java, C# chạy chậm hơn nên ngưỡng phải thấp hơn. Đề cho 2 giây thì nhân ngưỡng lên 2, 100 ms thì chia 10, cứ thế mà tính.
Bài này phải nói là rất dễ: chuỗi dài tối đa có $100$ ký tự, quá nhỏ, làm cách tệ cỡ nào cũng khó mà bị quá thời gian (trừ khi tệ quá mức 😅). Tuy nhiên chưa chắc bạn nào cũng giải được theo cách gọn gàng – mình đã xem code của bạn hỏi bài thì thấy bạn xử lý khá rườm rà, phức tạp. Nên ở đây mình phân tích một cách làm rất gọn, kèm vài kinh nghiệm khi gặp dạng bài kiểu này, các bạn chú ý nha.
Giải thích ví dụ cho bạn nào chưa hiểu đề: với ahhellllloou, ta giữ lại 5 chữ cái tô xanh và xoá hết phần còn lại (không đổi chỗ chữ nào) thì được đúng hello ⇒ YES.
hlelo: sau chữ e (vị trí 3) chỉ còn đúng một chữ l, không đủ 2 chữ l nên không cách nào xoá ra được hello.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 để nhảy tới:
Cách 1Tìm dãy con "hello" thay vì xoá ký tự – O(N)
Đầu tiên các bạn luôn phải nhớ: với mấy dạng bài kiểu này, đề thường không muốn ta nhìn ra hướng giải thực sự, mà cố tình dẫn ta đi lạc khỏi hướng làm tốt nhất. Thậm chí test ví dụ cũng cố tình đưa ra những trường hợp khiến ta lầm tưởng phải xử lý theo một hướng khác, nên các bạn phải tỉnh táo nhìn kỹ bản chất vấn đề nhé 😉.
Ở bài này, đề định nghĩa: xoá đi một số ký tự (không đổi vị trí các ký tự còn lại) sao cho chuỗi còn lại là hello thì in YES, không xoá được như vậy thì in NO. Bạn nào "ngây thơ", "ngoan ngoãn" làm đúng y như đề mô tả – tức là cố đi xoá ký tự trong chuỗi – là toang ngay: hoặc không làm nổi vì rối, hoặc có làm được thì code cũng dài dòng phức tạp cả về trình bày lẫn hiệu năng 😵.
Ta cần nhìn ra vấn đề thực sự: tại sao phải đi xoá để còn lại hello? Đề đã nói rõ là không thay đổi vị trí các ký tự, chữ nào ở yên chỗ đó. Vậy ta có thể lật ngược vấn đề:
hello trong chuỗi gốc hay không? Các ký tự của nó không cần nằm liên tiếp, có thể rời rạc cách xa nhau, miễn đúng thứ tự: gặp h trước, rồi tới e, rồi l, rồi l, sau cùng là o. Gặp đủ 5 ký tự theo đúng thứ tự đó thì kết luận ngay YES; tới cuối chuỗi vẫn chưa đủ thì kết luận NO. Chỉ cần duyệt từng ký tự của chuỗi gốc, chẳng cần xoá gì cả.Cụ thể: duyệt chuỗi, gặp ký tự h đầu tiên thì tăng biến đếm lên 1; duyệt tiếp gặp e thì tăng lên 1; gặp l thì tăng 1; gặp l nữa thì tăng 1; cuối cùng gặp o thì tăng 1 nữa. Lúc biến đếm bằng $5$ tức là đủ 5 ký tự đã xuất hiện lần lượt ⇒ in YES và dừng chương trình ngay. Nếu chương trình còn chạy được ra tới ngoài vòng lặp nghĩa là không có tình huống nào thoả YES ⇒ in NO.
if else cho từng chữ h, e, l, l, o, ta tạo chuỗi res = "hello" và biến idx chỉ vào ký tự đang cần tìm trong res, bắt đầu từ $0$. Cứ thấy s[i] == res[idx] thì idx++ để chuyển qua tìm ký tự kế tiếp của res; khi idx == 5 thì kết luận YES. Thực chất đây là kỹ thuật hai con trỏ: i chạy trên chuỗi gốc, idx chạy trên chuỗi mẫu.Vì sao gặp đúng ký tự cần tìm là "nhận" luôn mà không sợ bỏ lỡ cách chọn tốt hơn? Vì nhận ký tự sớm nhất có thể sẽ để lại phần đuôi chuỗi dài nhất cho các ký tự còn lại – không bao giờ thiệt hơn việc chờ một ký tự giống vậy ở phía sau. Đây là một ý tham lam rất tự nhiên.
Em nhập một chuỗi rồi bấm Tiếp để xem con trỏ i chạy trên chuỗi gốc và con trỏ idx nhích dần trên "hello":
Source code để các bạn tham khảo, nhìn vào là thấy rõ tất cả những gì mình đã phân tích ở trên:
#include <bits/stdc++.h>
using namespace std;
int main(){
string s;
cin >> s;
string res = "hello";
int idx = 0;
for(int i = 0; i < (int)s.length(); ++i){
if(s[i] == res[idx]){
idx++;
if(idx == 5){
cout << "YES";
return 0;
}
}
}
cout << "NO";
return 0;
}
s = input().strip()
res = "hello"
idx = 0
ok = False
for ch in s:
if ch == res[idx]:
idx += 1
if idx == 5:
ok = True
break
print("YES" if ok else "NO")
Bấm Tiếp để chạy tay từng dòng code C++ (phần "Dữ liệu" vẽ chuỗi s với con trỏ i và chuỗi res với con trỏ idx):
Giải thích 1 số chỗ trong code:
(int)s.length(): hàmlength()trả về kiểu số nguyên không dấu, ép vềintđể so sánh vớiicho "sạch", không bị trình biên dịch cảnh báo.- Ngay khi
idx == 5ta in YES vàreturn 0luôn: vừa đỡ phải xét tiếp phần còn lại, vừa không bao giờ truy cậpres[5](vượt quá 5 ký tự của"hello"). - Ra được tới ngoài vòng lặp tức là chưa từng có lúc
idx == 5⇒ in NO, không cần thêm biến cờ hiệu nào cả.
Đánh giá độ phức tạp của cách làm này:
- Không gian: ngoài chuỗi đầu vào thì chỉ dùng chuỗi
res5 ký tự và vài biến ⇒ $O(1)$. - Thời gian: duyệt chuỗi đúng 1 lượt ⇒ $O(N)$ với $N$ là độ dài chuỗi đầu vào.
- 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