Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học

Dãy nhị phân 3

Dãy nhị phân 3
Nguồn: CLUE OJ – Chương 20: Quay lui (Backtracking) Độ khó: Dễ Quay lui Cắt nhánh

Đề bài

Hệ nhị phân (hay hệ đếm cơ số hai) là một hệ đếm dùng hai ký tự để biểu đạt một giá trị số, bằng tổng số các lũy thừa của 2. Hai ký tự đó thường là 0 và 1; chúng thường được dùng để biểu đạt hai giá trị hiệu điện thế tương ứng (có hiệu điện thế, hoặc hiệu điện thế cao là 1 và không có, hoặc thấp là 0). Do có ưu điểm tính toán đơn giản, dễ dàng thực hiện về mặt vật lý, chẳng hạn như trên các mạch điện tử, hệ nhị phân trở thành một phần kiến tạo căn bản trong các máy tính đương thời.

Trong bài tập này bạn được cho một số nguyên dương $n$, bạn cần tìm và in ra danh sách các dãy nhị phân có độ dài đúng $n$ thoả mãn điều kiện không có hai số 1 đứng cạnh nhau.

Dữ liệu vào:

  • Gồm một dòng chứa số nguyên dương $n$.

Kết quả:

  • Dòng đầu tiên ghi một số nguyên dương $m$ là số lượng dãy nhị phân tìm được.
  • Mỗi dòng tiếp theo in một dãy nhị phân độ dài $n$ tìm được. Các dãy nhị phân in tăng dần theo thứ tự từ điển.

Ràng buộc: $1 \le n \le 25$.

Ví dụ – input
3
Ví dụ – output
5
000
001
010
100
101

Lời mở đầu

Bài này là bước tiếp theo sau Dãy nhị phân 1: vẫn là cây quay lui điền từng vị trí bằng 0 hoặc 1, nhưng giờ có điều kiện "không có 11". Đây là dịp tốt để làm quen với kỹ thuật quan trọng nhất của quay lui: cắt nhánh – phát hiện sai càng sớm càng tốt, không đi tiếp vào những nhánh chắc chắn hỏng. Bảng dưới so sánh 3 bài dãy nhị phân trong contest:

BàiĐiều kiện của dãyIn số lượng?Giới hạnSố dãy cần inĐiểm mấu chốt
Dãy nhị phân 1Không có – in tất cả $2^n$ dãyKhông$1 \le n \le 20$ (test thật tới 24)$2^n$Thuần quay lui, không cắt nhánh; khó ở chỗ in nhanh hàng trăm MB
Dãy nhị phân 2Cụm 01 xuất hiện không quá 1 lầnCó (dòng đầu)$1 \le n \le 100$$(n+1) + \binom{n+1}{3}$Bắt buộc cắt nhánh (không thể duyệt $2^{100}$); cần nhớ số cụm 01 đã có
Dãy nhị phân 3 (bài này)Không có hai số 1 đứng cạnh nhauCó (dòng đầu)$1 \le n \le 25$Fibonacci $F_{n+2}$Cắt nhánh chỉ cần nhìn ký tự ngay trước; sinh hết rồi lọc vẫn kịp trong C++
Dành cho học viên · Ưu tiênNộp bài trên hệ thống CLUE OJ

Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.

Nộp bài trên CLUE OJ

Lộ trình lời giải, bấm vào từng phần để nhảy tới:

Bộ testBộ test chính thức có gì?

Bộ test có 25 test, mỗi test một số $n$: test 1 là $n = 3$ (test đề), test 2 là $n = 2$, test 3 là $n = 1$, các test 4 – 25 có $n$ đúng bằng số thứ tự test. Đúng ràng buộc đề, không có gì bất thường. Số dãy cần in tăng theo dãy Fibonacci (5, 8, 13, 21, …):

Test$n$Số dãy lớn nhấtFile output lớn nhất
1 – 201 … 2017.7110,4 MB
21 – 2521 … 25196.4185,3 MB

Đề không chia subtask; mình chia 2 mức theo $n$ để thấy khác biệt giữa các cách:

MứcĐiều kiệnCác testCách 1 C++
sinh hết rồi lọc
Cách 1 PythonCách 2 C++
cắt nhánh
Cách 2 Python
1$n \le 20$1 – 2020/2020/2020/2020/20
2$21 \le n \le 25$21 – 255/51/55/55/5

Ý tưởngĐặt số 1 chỉ khi ngay trước nó là số 0

Cách "ngây thơ": sinh cả $2^n$ dãy như bài Dãy nhị phân 1, đủ $n$ ký tự rồi mới kiểm tra có 11 không. Nhưng nghĩ một chút: nếu đã đặt 1 ở vị trí $i-1$ mà lại đặt tiếp 1 ở vị trí $i$ thì dãy chắc chắn hỏng, dù các vị trí sau có điền gì đi nữa. Vậy cắt luôn nhánh đó:

Điều kiện cắt nhánh
Ở vị trí $i$: luôn được đặt 0; chỉ được đặt 1 khi $i = 0$ (vị trí đầu) hoặc $s_{i-1} = $ 0. Nhờ vậy mọi lá của cây đều là dãy hợp lệ, không phải kiểm tra gì ở cuối nữa.

Thứ tự từ điển vẫn được đảm bảo vì ở mỗi vị trí ta thử 0 trước rồi mới tới 1. Còn số lượng thì sao? Gọi $f(n)$ là số dãy hợp lệ độ dài $n$: dãy bắt đầu bằng 0 thì phần sau là dãy hợp lệ độ dài $n-1$; dãy bắt đầu bằng 1 thì bắt buộc tiếp theo là 0, phần sau là dãy hợp lệ độ dài $n-2$. Vậy $f(n) = f(n-1) + f(n-2)$ với $f(1) = 2$, $f(2) = 3$ – chính là dãy Fibonacci. Với $n = 25$ có $196\,418$ dãy.

Em nhập $n$, chọn có cắt nhánh (1) hoặc sinh hết rồi lọc (0), rồi bấm Tiếp để so sánh hai cây (nút đỏ có dấu ✗ là nhánh bị cắt, lá đỏ là dãy bị loại):

Ví dụ nhanh:

Cách 1Sinh hết $2^n$ dãy rồi lọc

Đi hết cây đầy đủ, đủ $n$ ký tự thì gọi ok() kiểm tra. Vì đề bắt in số lượng trước rồi mới in các dãy, mình gom các dãy hợp lệ vào xâu res và đếm bằng cnt, cuối cùng in cnt rồi in res.

C++▶ Chạy tay từng dòng codeCách 1: sinh đủ 2^n dãy, đủ n ký tự mới gọi ok() kiểm tra.
Dữ liệu mẫu:
Không gian$O(n \cdot f(n))$ cho xâu kết quả
Thời gian$O(n \cdot 2^n)$

Kết quả chạy thật: bản C++ đúng 25/25 test, chậm nhất 0,25 giây ($n = 25$: $2^{25} \approx 33{,}5$ triệu lá) – vẫn kịp vì ok() dừng ngay khi gặp 11 đầu tiên. Bản Python đúng 21/25 test, quá thời gian ở test 22–25.

Cách 2Quay lui có cắt nhánh

C++▶ Chạy tay từng dòng codeCách 2: không đặt 1 ngay sau 1 – mọi lá đều hợp lệ.
Dữ liệu mẫu:
  1. Dòng if(c == '1' && i > 0 && s[i - 1] == '1') continue; là toàn bộ "phép màu": không đặt 1 ngay sau 1. Điều kiện i > 0 đặt trước để không đọc s[-1].
  2. Khi i == n thì dãy chắc chắn hợp lệ, chỉ việc đếm và lưu.
  3. Mỗi nút trong cây đều dẫn tới ít nhất một lá (luôn có thể đặt 0), nên số lời gọi chỉ cỡ $n \cdot f(n)$ – không lãng phí.
Không gian$O(n \cdot f(n))$ cho xâu kết quả
Thời gian$O(n \cdot f(n))$, $f(n)$ là số Fibonacci

Kết quả chạy thật: bản C++ đúng 25/25 test, chậm nhất chỉ 0,018 giây (nhanh hơn cách 1 khoảng 14 lần); bản Python cũng đúng 25/25, chậm nhất 0,20 giây. Full điểm! 🎉

Ghi chú của mình: với $n \le 25$ thì trong C++ cả hai cách đều qua (25/25 và 25/25), nhưng trong Python chỉ cách cắt nhánh mới qua hết (21/25 so với 25/25). Ở bài Dãy nhị phân 2 với $n \le 100$, không cắt nhánh là không có cửa. Hãy tập thói quen: thấy sai ở đâu, cắt ở đó.
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇

Nhận xét