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

Hình chữ nhật 0 1 ver 1

Hình chữ nhật 0 1 ver 1
Nguồn: CLUE OJ – Chương 15: Ngăn xếp, Hàng đợi Độ khó: Khá Ngăn xếp đơn điệu Mảng 2 chiều

Đề bài

Cho một bảng $a$ gồm $N$ hàng, $M$ cột. Ô ở hàng thứ $i$ và cột thứ $j$ được gọi là ô $(i, j)$, các ô trong bảng nhận giá trị $0$ hoặc $1$. Hình chữ nhật $(x_1, y_1, x_2, y_2)$ bao gồm các ô $(i, j)$ mà $x_1 \le i \le x_2$ và $y_1 \le j \le y_2$.

Yêu cầu: Hãy tính diện tích của hình chữ nhật lớn nhất bao gồm toàn số 0.

Dữ liệu:

  • Dòng đầu tiên gồm hai số nguyên dương $N, M$ $(1 \le N, M \le 1000)$ là số hàng và số cột của bảng $a$.
  • $N$ dòng tiếp theo, mỗi dòng chứa $M$ số $0$ hoặc $1$ biểu diễn giá trị của các ô trong bảng.

Kết quả: Gồm một dòng duy nhất là diện tích của hình chữ nhật thỏa mãn.

Ràng buộc: $1 \le N \le 1000$; $1 \le M \le 1000$; các ô của bảng có giá trị $0$ hoặc $1$.

Chấm điểm:

SubtaskRàng buộc thêm% số điểm
1$N, M \le 100$50
2Không có50

Hạn chế thời gian: 1 giây. Hạn chế bộ nhớ: 256 MB.

Ví dụ – input
5 4
1 0 0 1
0 0 0 0
0 1 1 0
1 1 0 0
1 0 0 0
Ví dụ – output
4

Giải thích ví dụ: có nhiều hình chữ nhật toàn số 0 diện tích $4$, ví dụ cả hàng 2 (các ô $(2, 1)$ – $(2, 4)$), hay khối $2 \times 2$ ở góc dưới phải (hàng 4–5, cột 3–4). Không có hình toàn 0 nào diện tích $5$ trở lên.

Lời mở đầu

Đây là bài "2 chiều" của bài Hình chữ nhật lớn nhất (histogram): thay vì một dãy cột, ta có một bảng 0/1 và cần tìm hình chữ nhật toàn số 0 lớn nhất. Mẹo then chốt là biến mỗi hàng của bảng thành một biểu đồ cột rồi dùng lại nguyên code ngăn xếp của bài trước. Bài này còn có "người anh em" ver 2 – vừa tìm diện tích vừa đếm số hình – mình viết riêng ở bài sau.

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ì?

TestNMTỉ lệ số 0Diện tích lớn nhất
1 – 202 … 102 … 1039% … 69%3 … 10
21 – 5010 … 9811 … 9648% … 51%8 … 16
51 – 80113 … 943101 … 96649% … 50%14 … 24
81 – 1001.0001.00049% … 49%20 … 24
  • Bộ test có 100 test: test 1–50 thuộc subtask 1 ($N, M \le 100$), test 51–100 thuộc subtask 2 (test 81–100 đều là bảng $1000 \times 1000$ – một triệu ô).
  • Mọi bảng đều sinh ngẫu nhiên với khoảng một nửa số ô là 0, nên hình chữ nhật toàn 0 lớn nhất rất nhỏ (không quá 24 ô). Bộ test không có bảng toàn 0 hay các khối 0 lớn.
SubtaskĐiều kiệnCác testCách 1 C++Cách 2 C++Cách 1 PythonCách 2 Python
1 (50%)$N, M \le 100$1 – 5050/5050/5040/5050/50
2 (50%)Không có51 – 1003/5050/500/5050/50

Ý tưởngMỗi hàng là một bài "Hình chữ nhật lớn nhất"

Bước 1 – biến bảng thành các biểu đồ cột. Cố định hàng đáy $i$ của hình chữ nhật. Với mỗi cột $j$, gọi

$h[j]$ = số ô 0 liên tiếp tính từ ô $(i, j)$ đi lên trên

(nếu $a[i][j] = 1$ thì $h[j] = 0$). Một hình chữ nhật toàn 0 có đáy ở hàng $i$, trải từ cột $l$ tới cột $r$, thì chiều cao của nó không vượt quá $\min(h[l..r])$ – y hệt bài Hình chữ nhật lớn nhất với $M$ cột cao $h[1..M]$!

Bước 2 – cập nhật $h$ từ hàng trước. Đi từ hàng $i - 1$ xuống hàng $i$ rất đơn giản:

$h[j] = \begin{cases} h[j] + 1 & \text{nếu } a[i][j] = 0 \\ 0 & \text{nếu } a[i][j] = 1 \end{cases}$

Thuật toán
Duyệt từng hàng $i = 1..N$: cập nhật mảng $h$, rồi giải bài "hình chữ nhật lớn nhất trong histogram" trên $h[1..M]$ bằng ngăn xếp đơn điệu ($O(M)$). Đáp án là max qua mọi hàng. Tổng cộng $O(N \cdot M)$.

Hình chữ nhật toàn 0 lớn nhất có hàng đáy là một hàng $i$ nào đó, nên nó được tính tới khi ta xét hàng $i$ – không bỏ sót.

Em nhập bảng (các hàng cách nhau bởi dấu /) rồi bấm Tiếp để xem từng hàng biến thành biểu đồ cột thế nào:

Ví dụ nhanh:

Cách 1Vét mọi hình chữ nhật – O(N²M²)

Một hình chữ nhật được xác định bởi hàng trên $x_1$, hàng dưới $x_2$, cột trái $y_1$, cột phải $y_2$. Để biết nó có toàn 0 hay không, ta đếm số ô 1 bên trong bằng tổng tiền tố 2 chiều: $s[i][j]$ = số ô 1 trong hình $(1, 1)$ – $(i, j)$, khi đó số ô 1 của hình $(x_1, y_1, x_2, y_2)$ là

$s[x_2][y_2] - s[x_1 - 1][y_2] - s[x_2][y_1 - 1]$ $+ s[x_1 - 1][y_1 - 1]$

Hình toàn 0 khi và chỉ khi con số này bằng 0.

C++▶ Chạy tay từng dòng codeCách 1: tổng tiền tố s rồi vét mọi hình (x1, y1, x2, y2), hình nào có 0 ô số 1 thì cập nhật ans.
Dữ liệu mẫu:
Vét cạn có rất nhiều bước, nên khung này giới hạn bảng tối đa 5 × 5. Ô đỏ đậm: ô số 1 nằm trong hình đang xét.
  1. Tính s[i][j] ngay khi đọc, dùng công thức "trên + trái − chéo + ô hiện tại".
  2. Mảng a, s kích thước $1005 \times 1005$ khai báo toàn cục (khoảng 8 MB) – để trong hàm main sẽ dễ tràn bộ nhớ ngăn xếp.
Không gian$O(N \cdot M)$
Thời gian$O(N^2 M^2)$

Kết quả chạy thật: bản C++ đúng 53/100 test (quá 1 giây ở các test 51–58, 60–65, 67–75, 77–100), test đúng chạy lâu nhất 0.571s; bản Python đúng 40/100 test (quá thời gian ở các test 21–23, 25, 28, 38, 42, 44, 49–100), lâu nhất 0.52s.

Với $N = M = 100$ có khoảng $(100 \cdot 101 / 2)^2 \approx 2{,}5 \cdot 10^7$ hình – C++ chạy kịp. Với $N = M = 1000$ thì con số này là $2{,}5 \cdot 10^{11}$ – không thể.

Cách 2Chiều cao cột + ngăn xếp – O(N·M)

Cài đặt đúng ý tưởng ở trên. Đoạn giữa (tìm L, R bằng ngăn xếp rồi lấy max) chép gần y nguyên từ Cách 3 của bài Hình chữ nhật lớn nhất, chỉ đổi i thành j, n thành m.

C++▶ Chạy tay từng dòng codeCách 2: mỗi hàng cập nhật h[j], rồi dùng ngăn xếp tìm L[j], R[j] và lấy max h[j]·(R[j] − L[j] − 1).
Dữ liệu mẫu:
  1. Không cần lưu cả bảng: đọc hàng nào xử lý hàng đó, mảng h mang thông tin của các hàng phía trên.
  2. h[j] = (a == 0) ? h[j] + 1 : 0; – gặp số 1 thì "cột 0" bị cắt đứt, chiều cao về 0.
  3. Mỗi hàng: mỗi cột được push 1 lần và pop nhiều nhất 1 lần trong mỗi lần duyệt → $O(M)$ mỗi hàng, $O(N \cdot M)$ cả bài. Diện tích tối đa $10^6$ nên dùng int là đủ.
  4. Đọc $10^6$ số bằng cin cần có ios::sync_with_stdio(0); cin.tie(0); cho nhanh.
Không gian$O(M)$ (không lưu cả bảng)
Thời gian$O(N \cdot M)$

Kết quả chạy thật: bản C++ đúng 100/100 test, test đúng chạy lâu nhất 0.083s; bản Python đúng 100/100 test, lâu nhất 0.74s.

Tổng kếtKết quả chạy thật trên bộ test

CáchNgôn ngữĐúngLâu nhất (test đúng)Ghi chú
1. Vét mọi hình + tổng tiền tố – $O(N^2 M^2)$C++53/1000.571sQuá thời gian ở test 51–58, 60–65, 67–75, 77–100
Python40/1000.522sQuá thời gian ở test 21–23, 25, 28, 38, 42, 44, 49–100
2. Chiều cao cột + ngăn xếp – $O(N \cdot M)$C++100/1000.083sFull điểm
Python100/1000.739sFull điểm
Ghi chú của mình: thời gian trong bảng là thời gian CPU đo trên máy của mình (giới hạn 1 giây/test). Cách 2 với C++ chạy mọi test dưới 0,08 giây (phần lớn thời gian là đọc một triệu số). Bản Python của Cách 2 cũng qua hết. Bài sau – Hình chữ nhật 0 1 ver 2 – dùng lại đúng code này nhưng phải đếm số hình đạt diện tích lớn nhất, và sẽ phải xử lý chuyện đếm trùng khi có các cột cao bằng nhau.
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