Cửa Hàng Số Học
Đề bài
Sắp tới là sinh nhật em Hạnh nên anh Hoàng quyết định tìm một món quà thật ý nghĩa. Biết Hạnh có đam mê với bộ môn nhị phân, Hoàng đến ngay cửa hàng số học tìm ra bộ nhị phân ưng ý. Nhưng cửa hàng chỉ bán dãy nhị phân $S$ có độ dài $N$ ($1 \le N \le 10^6$). Để lấy ra được bộ nhị phân ưng ý thì Hoàng cần phải chọn các cặp 10 bất kỳ trong dãy $S$ và xóa 1 trong 2 số.
Hãy giúp Hoàng tìm ra dãy có độ dài ngắn nhất sau khi xóa. Nếu có nhiều dãy có độ dài giống nhau thì hãy in ra dãy có thứ tự chữ cái nhỏ nhất. Dãy $A$ có thứ tự chữ cái nhỏ hơn $B$ nếu $A_1 = B_1,$ $A_2$ $= B_2,$ $A_3$ $= B_3,$ $\ldots,$ $A_i$ $\lt B_i,$ $\ldots$
Đầu vào
- Dòng đầu tiên là số tự nhiên $N$ ($1 \le N \le 10^6$).
- Dòng thứ hai là dãy $S$ có độ dài $N$ chỉ bao gồm số 1 hoặc 0.
Đầu ra: in ra một dãy nhị phân đẹp nhất mà Hoàng có thể tìm được sau khi thực hiện tất cả các bước xóa.
10
000111111100011111114
01010018
11001101011
11Lời mở đầu
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 😇.
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 OJMấu chốt: chuỗi dạng 1……0 luôn xoá được về đúng 1 số 0
Bài này thoạt nhìn phức tạp và tưởng là dạng xử lý bit, nhưng nếu đọc kỹ đề thì sẽ nhận ra đây chỉ là bài tập xử lý chuỗi đơn giản thôi.
Mấu chốt để giải quyết bài này là em phải nhìn ra quy tắc xoá mà đề bài đưa ra. Nếu với quy tắc xoá như vậy thì chỉ cần chuỗi có dạng 1………0, tức là đầu tiên là số 1 và cuối cùng là số 0, còn lại ở giữa tụi nó là gì tuỳ ý, thì với quy tắc xoá đề bài đưa ra, sau cùng kết quả chỉ là 0 (ví dụ em có thể thử với các trường hợp 1000000 hay 11111110 hay 1001100101010010 xem có đúng như anh nói không?).
Ví dụ với test 3, phần giữa 1100110 (bắt đầu bằng 1, kết thúc bằng 0) được xoá dần về 1 số 0 như sau (2 ô viền là cặp 10 được chọn, ô gạch là số bị xoá):
Vì sao lại như vậy? (giải thích thêm)
Xoá được về "0": trong chuỗi bắt đầu bằng 1 và kết thúc bằng 0, chắc chắn có ít nhất 1 chỗ số 1 đứng ngay trước số 0. Nếu chuỗi còn số 1 nào khác ngoài số 1 đầu tiên, hoặc còn nhiều số 0, ta luôn chọn được 1 cặp 10 để xoá mà chuỗi vẫn giữ được dạng "đầu là 1, cuối là 0"; cứ thế đến khi còn 10 thì xoá số 1, còn lại đúng 0.
Không thể làm tốt hơn: các số 0 ở đầu chuỗi không có số 1 nào đứng trước nên không bao giờ nằm trong cặp 10, tương tự các số 1 ở cuối chuỗi không có số 0 nào đứng sau. Vậy khối 0 đầu và khối 1 cuối không thể bị xoá. Còn phần ở giữa (nếu có) thì ít nhất phải còn lại 1 số, và để thứ tự nhỏ nhất thì số còn lại đó nên là 0.
Em nhập một dãy nhị phân rồi bấm Tiếp để xem phần giữa bị xoá dần về một số 0 như thế nào:
Lời giải O(N)
Thì nếu đã nhìn ra điều đó thì giờ đây cách giải quyết bài này rất đơn giản:
- Kiểm tra khúc đầu tiên của chuỗi, nếu là số 0 thì đếm xem có bao nhiêu số 0 liên tiếp (
cnt0). - Kiểm tra khúc cuối cùng của chuỗi, nếu là số 1 thì đếm xem có bao nhiêu số 1 liên tiếp (
cnt1). - Nếu 2 số lượng này cộng lại bằng đúng độ dài chuỗi ban đầu (chuỗi có dạng
000…0111…1, không có cặp 10 nào) thì kết quả xuất ra chuỗi ban đầu. - Ngược lại, xuất ra đủ
cnt0số 0 đầu tiên, sau đó xuất thêm một số 0 nữa là kết quả sau khi áp dụng quy tắc xoá với chuỗi ở giữa, cuối cùng xuất ra đủcnt1số 1 cuối cùng.
| S | cnt0 | Phần giữa | cnt1 | Kết quả |
|---|---|---|---|---|
| 0001111111 | 3 | (rỗng) | 7 | 0001111111 |
| 0101 | 1 | 10 | 1 | 001 |
| 11001101 | 0 | 1100110 | 1 | 01 |
| 1 | 0 | (rỗng) | 1 | 1 |
Độ phức tạp sẽ là $O(N)$ em nhé, với $N$ là độ dài chuỗi ban đầu. Code cho em tham khảo:
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
string s;
cin >> s >> s;
int cnt0 = 0, cnt1 = 0;
for(int i = 0; i < s.length(); ++i)
{
if(s[i] == '0')
cnt0++;
else
break;
}
for(int i = s.length() - 1; i >= 0; --i)
{
if(s[i] == '1')
cnt1++;
else
break;
}
if(cnt0 + cnt1 == s.length())
cout << s;
else
{
while(cnt0--)
cout << "0";
cout << "0";
while(cnt1--)
cout << "1";
}
return 0;
}
_ = input().strip()
s = input().strip()
cnt0 = 0
cnt1 = 0
for ch in s:
if ch == '0':
cnt0 += 1
else:
break
for ch in reversed(s):
if ch == '1':
cnt1 += 1
else:
break
if cnt0 + cnt1 == len(s):
print(s)
else:
print("0" * cnt0 + "0" + "1" * cnt1)
Lưu ý nhỏ: trong code C++, dòng cin >> s >> s; là đọc luôn số $N$ vào s rồi đọc đè chuỗi nhị phân lên, vì độ dài chuỗi lấy bằng s.length() nên không cần dùng tới $N$. Mình đã kiểm tra code với tất cả các chuỗi độ dài từ 1 đến 9 (và ngẫu nhiên đến 12) so với cách thử mọi cách xoá, đều cho đúng kết quả; với $N = 10^6$ cả 2 bản đều chạy rất nhanh.
- 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