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 2

Hình Chữ Nhật 2
Nguồn: Codeforces Group Độ khó: Khó Mảng cộng dồn 2 chiều Danh sách liên kết

Đề bài

Cho một hình chữ nhật gồm $N$ dòng và $M$ cột. Các dòng được đánh số từ 1 đến $N$, từ trên xuống dưới. Các cột được đánh số từ 1 đến $M$, từ trái sang phải. Ô ở dòng thứ $i$ và cột thứ $j$ được gọi là ô $(i, j)$ và có diện tích là 1 đơn vị. Có một số ô đã được điền sẵn kí tự 'X'.

Yêu cầu: tìm hình chữ nhật con có diện tích lớn nhất chỉ chứa duy nhất một kí tự 'X'.

Dữ liệu vào

  • Dòng đầu tiên gồm ba số nguyên dương $N, M, K$ ($N, M \le 10^4$; $K \le 10^3$) mô tả kích thước của hình chữ nhật và số lượng kí tự 'X' có trong hình chữ nhật;
  • $K$ dòng sau, mỗi dòng gồm hai số nguyên dương $d$ và $c$ là chỉ số dòng và cột của ô điền kí tự 'X' ($d \le N$; $c \le M$).

Kết quả: ghi ra diện tích của hình chữ nhật lớn nhất thoả mãn yêu cầu đề bài.

Ràng buộc

  • Có 50% số test tương ứng với 50% số điểm thoả mãn: $N, M \le 50$;
  • 30% số test khác tương ứng với 30% số điểm thoả mãn: $N, M \le 500$;
  • 20% số test còn lại tương ứng với 20% số điểm không có ràng buộc gì thêm.
Ví dụ – input
4 5 4
2 3
2 5
3 1
4 4
Ví dụ – output
9
Giải thích
Bảng 4 x 5 với 4 chữ X, vùng tô xám 3 x 3 chứa đúng 1 chữ X

Vùng tô xám là hình chữ nhật từ ô $(1, 2)$ đến ô $(3, 4)$, gồm $3 \times 3 = 9$ ô và chỉ chứa đúng 1 chữ X ở ô $(2, 3)$.

Lờ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 😇.

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
Dành cho tất cả mọi ngườiNộp bài trên Codeforces

Bạn nào muốn thử nộp bài để hệ thống chấm xem có đúng hay không thì dùng link này nhé. Chưa có tài khoản thì tạo tài khoản rồi đăng nhập nộp bài thử hen.

Nộp bài trên Codeforces

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

50%Subtask 1: duyệt mọi hình chữ nhật + mảng cộng dồn 2 chiều

Ta sẽ duyệt qua ô góc dưới cùng bên phải và ô góc trên cùng bên trái của hình chữ nhật con. Nếu hình chữ nhật đó có đúng 1 dấu X thì sẽ cập nhật diện tích của hình chữ nhật lớn nhất. Bài toán chuyển về đếm số lượng dấu X trong mỗi hình chữ nhật cho trước.

Ta có thể duyệt qua hết các ô của hình chữ nhật con được chọn (độ phức tạp thuật toán sẽ là $O(n^3 m^3)$), tuy nhiên ta có thể tối ưu bằng mảng tổng dồn 2 chiều để đếm số dấu X trong 1 hình chữ nhật bất kỳ chỉ trong $O(1)$, khi đó độ phức tạp còn $O(n^2 m^2)$.

Mảng cộng dồn 2 chiều
Gọi a[i][j] là số dấu X trong hình chữ nhật từ ô $(1, 1)$ đến ô $(i, j)$:
a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1]
Khi đó số dấu X trong hình chữ nhật từ $(r1, c1)$ đến $(r2, c2)$ là:
a[r2][c2] - a[r2][c1-1] - a[r1-1][c2] + a[r1-1][c1-1]

Với $N, M \le 50$ thì $n^2 m^2 / 4 \approx 1.5$ triệu phép tính, chạy rất nhanh.

Xem code subtask 1
#include<bits/stdc++.h>
using namespace std;
int n, m, k;
vector<pair<int, int>> X(1005);
namespace sub1{
    int a[1005][1005];
    int sum(int r1, int c1, int r2, int c2){
        return a[r2][c2] - a[r2][c1-1] - a[r1-1][c2] + a[r1-1][c1-1];
    }
    int solve(){
        for(int i = 0; i < k; i++){
            a[X[i].first][X[i].second]++;
        }
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++)
                a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1];
        }
        int ans = 0;
        for(int r2 = 1; r2 <= n; r2++){
            for(int c2 = 1; c2 <= m; c2++){
                for(int r1 = 1; r1 <= r2; r1++){
                    for(int c1 = 1; c1 <= c2; c1++) if(sum(r1, c1, r2, c2) == 1){
                        ans = max(ans, (r2-r1+1) * (c2-c1+1));
                    }
                }
            }
        }
        return ans;
    }
};

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> m >> k;
    for(int i = 0; i < k; i++) cin>>X[i].first>>X[i].second;
    cout << sub1::solve() << endl;
}
import sys
input = sys.stdin.readline

n, m, k = map(int, input().split())
X = [tuple(map(int, input().split())) for _ in range(k)]

class sub1:
    a = [[0] * 1005 for _ in range(1005)]

    @staticmethod
    def sum(r1, c1, r2, c2):
        return (sub1.a[r2][c2]
                - sub1.a[r2][c1 - 1]
                - sub1.a[r1 - 1][c2]
                + sub1.a[r1 - 1][c1 - 1])

    @staticmethod
    def solve():
        for x, y in X:
            sub1.a[x][y] += 1

        for i in range(1, n + 1):
            for j in range(1, m + 1):
                sub1.a[i][j] += sub1.a[i - 1][j] + sub1.a[i][j - 1] - sub1.a[i - 1][j - 1]

        ans = 0
        for r2 in range(1, n + 1):
            for c2 in range(1, m + 1):
                for r1 in range(1, r2 + 1):
                    for c1 in range(1, c2 + 1):
                        if sub1.sum(r1, c1, r2, c2) == 1:
                            ans = max(ans, (r2 - r1 + 1) * (c2 - c1 + 1))
        return ans

print(sub1.solve())
C++▶ Chạy tay từng dòng codeSubtask 1: duyệt mọi hình chữ nhật (r1, c1)–(r2, c2), đếm số X bằng mảng cộng dồn 2 chiều trong O(1).
Dữ liệu mẫu:
1#include<bits/stdc++.h>
2using namespace std;
3int n, m, k;
4vector<pair<int, int>> X(1005);
5namespace sub1{
6 int a[1005][1005];
7 int sum(int r1, int c1, int r2, int c2){
8 return a[r2][c2] - a[r2][c1-1] - a[r1-1][c2] + a[r1-1][c1-1];
9 }
10 int solve(){
11 for(int i = 0; i < k; i++){
12 a[X[i].first][X[i].second]++;
13 }
14 for(int i = 1; i <= n; i++){
15 for(int j = 1; j <= m; j++)
16 a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1];
17 }
18 int ans = 0;
19 for(int r2 = 1; r2 <= n; r2++){
20 for(int c2 = 1; c2 <= m; c2++){
21 for(int r1 = 1; r1 <= r2; r1++){
22 for(int c1 = 1; c1 <= c2; c1++) if(sum(r1, c1, r2, c2) == 1){
23 ans = max(ans, (r2-r1+1) * (c2-c1+1));
24 }
25 }
26 }
27 }
28 return ans;
29 }
30};
31
32int main(){
33 ios::sync_with_stdio(0);
34 cin.tie(0);
35 cin >> n >> m >> k;
36 for(int i = 0; i < k; i++) cin>>X[i].first>>X[i].second;
37 cout << sub1::solve() << endl;
38}

+30%Subtask 2: giảm xuống 3 vòng for – O(n² · m)

Ta định nghĩa một hình chữ nhật trên bảng con bằng góc trái trên và góc phải dưới của nó. Giả sử ta có góc trái trên là $(r, c)$ và góc phải dưới là $(u, v)$. Dựa trên công thức của mảng tổng dồn hai chiều, ta có tổng số dấu X trong đó sẽ là sum[u][v] - sum[u][c - 1] - sum[r - 1][v] + sum[r - 1][c - 1]. Một bảng con thoả mãn điều kiện khi:

sum[u][v] − sum[u][c − 1] − sum[r − 1][v] + sum[r − 1][c − 1] = 1

Ta sẽ giảm xuống 3 vòng for bằng cách chỉ chạy duyệt $u$, $r$, $v$. Khi này, ta đã biết trước được sum[u][v], sum[r - 1][v] và ta cần tìm vị trí $c$ sao cho:

sum[u][c − 1] − sum[r − 1][c − 1] + 1 = sum[u][v] − sum[r − 1][v]

Nhận xét rằng nếu ta for $u$ và $r$ trước, sau đó lặp tiếp $v$, thì ta có thể lưu lại các giá trị sum[u][c - 1] - sum[r - 1][c - 1] có thể có sau mỗi lần tăng $v$. Khi này ta chỉ cần dùng mảng đánh dấu vị trí $c$ nhỏ nhất có thể đạt được giá trị đó (vì $c$ càng nhỏ thì hình chữ nhật càng rộng) để tối ưu hoá kết quả.

Trong code, mảng luu[giá trị + k] lưu cột $c$ nhỏ nhất ứng với mỗi giá trị (cộng thêm $k$ để chỉ số không bị âm). Với mỗi $c2$, trước tiên ta ghi nhận giá trị của cột $c2 - 1$ (ứng với $c = c2$), rồi tra xem có $c$ nào cho đúng 1 dấu X hay không.

Độ phức tạp: $O(n \cdot n \cdot m)$.

Xem code subtask 2
#include<bits/stdc++.h>
using namespace std;
int n, m, k;
vector<pair<int, int>> X(1005);

namespace sub2{
    int a[1005][1005];
    int luu[2005];
    int sum(int r1, int c1, int r2, int c2){
        return a[r2][c2] - a[r2][c1-1] - a[r1-1][c2] + a[r1-1][c1-1];
    }
    int solve(){
        for(int i = 0; i < k; i++){
            a[X[i].first][X[i].second]++;
        }
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++)
                a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1];
        }
        int ans = 0;
        for(int r2 = 1; r2 <= n; r2++){
            for(int r1 = 1; r1 <= r2; r1++){
                for (int j = 0; j <= 2 * k; j ++) luu[j] = -1e9;
                for(int c2 = 1; c2 <= m; c2++){
                    if (luu[a[r2][c2 - 1] - a[r1 - 1][c2 - 1] + k] < 0)
                        luu[a[r2][c2 - 1] - a[r1 - 1][c2 - 1] + k] = c2;
                    int pos = luu[a[r2][c2] - a[r1 - 1][c2] - 1 + k];
                    if (pos > 0) ans = max(ans, (r2-r1+1) * (c2-pos+1));
                }
            }
        }
        return ans;
    }
};

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> m >> k;
    for(int i = 0; i < k; i++) cin>>X[i].first>>X[i].second;
    cout << sub2::solve() << endl;
}
import sys
input = sys.stdin.readline

n, m, k = map(int, input().split())
X = [tuple(map(int, input().split())) for _ in range(k)]

class sub2:
    a = [[0] * 1005 for _ in range(1005)]
    luu = [0] * 2005

    @staticmethod
    def sum(r1, c1, r2, c2):
        return (sub2.a[r2][c2]
                - sub2.a[r2][c1 - 1]
                - sub2.a[r1 - 1][c2]
                + sub2.a[r1 - 1][c1 - 1])

    @staticmethod
    def solve():
        for x, y in X:
            sub2.a[x][y] += 1

        for i in range(1, n + 1):
            for j in range(1, m + 1):
                sub2.a[i][j] += sub2.a[i - 1][j] + sub2.a[i][j - 1] - sub2.a[i - 1][j - 1]

        ans = 0
        for r2 in range(1, n + 1):
            for r1 in range(1, r2 + 1):
                for j in range(0, 2 * k + 1):
                    sub2.luu[j] = -10**9
                for c2 in range(1, m + 1):
                    idx = sub2.a[r2][c2 - 1] - sub2.a[r1 - 1][c2 - 1] + k
                    if sub2.luu[idx] < 0:
                        sub2.luu[idx] = c2
                    pos = sub2.luu[sub2.a[r2][c2] - sub2.a[r1 - 1][c2] - 1 + k]
                    if pos > 0:
                        ans = max(ans, (r2 - r1 + 1) * (c2 - pos + 1))
        return ans

print(sub2.solve())
C++▶ Chạy tay từng dòng codeSubtask 2: cố định dải dòng r1..r2, quét c2 và nhớ cột trái nhỏ nhất cho mỗi giá trị d bằng mảng luu – O(n²·m).
Dữ liệu mẫu:
1#include<bits/stdc++.h>
2using namespace std;
3int n, m, k;
4vector<pair<int, int>> X(1005);
5
6namespace sub2{
7 int a[1005][1005];
8 int luu[2005];
9 int sum(int r1, int c1, int r2, int c2){
10 return a[r2][c2] - a[r2][c1-1] - a[r1-1][c2] + a[r1-1][c1-1];
11 }
12 int solve(){
13 for(int i = 0; i < k; i++){
14 a[X[i].first][X[i].second]++;
15 }
16 for(int i = 1; i <= n; i++){
17 for(int j = 1; j <= m; j++)
18 a[i][j] += a[i-1][j] + a[i][j-1] - a[i-1][j-1];
19 }
20 int ans = 0;
21 for(int r2 = 1; r2 <= n; r2++){
22 for(int r1 = 1; r1 <= r2; r1++){
23 for (int j = 0; j <= 2 * k; j ++) luu[j] = -1e9;
24 for(int c2 = 1; c2 <= m; c2++){
25 if (luu[a[r2][c2 - 1] - a[r1 - 1][c2 - 1] + k] < 0)
26 luu[a[r2][c2 - 1] - a[r1 - 1][c2 - 1] + k] = c2;
27 int pos = luu[a[r2][c2] - a[r1 - 1][c2] - 1 + k];
28 if (pos > 0) ans = max(ans, (r2-r1+1) * (c2-pos+1));
29 }
30 }
31 }
32 return ans;
33 }
34};
35
36int main(){
37 ios::sync_with_stdio(0);
38 cin.tie(0);
39 cin >> n >> m >> k;
40 for(int i = 0; i < k; i++) cin>>X[i].first>>X[i].second;
41 cout << sub2::solve() << endl;
42}

100%Subtask 3: chỉ xét các cột trái "đáng quan tâm" + danh sách liên kết – O(m · k)

Việc duyệt qua tất cả các hình chữ nhật con của bảng chắc chắn sẽ không đủ nhanh, vì số hình chữ nhật là vô cùng lớn. Vậy nên chúng ta nghĩ đến hướng tối ưu khác.

Ta sẽ định nghĩa 1 hình chữ nhật con bởi cột trái $c_1$, cột phải $c_2$ ($c_1 \le c_2$) và dòng trên, dòng dưới. Để cho đơn giản, ở đây ta sẽ chỉ nói về lời giải khi không có 2 chữ X cùng dòng. Nếu có 2 chữ X cùng dòng ta có thể giải tương tự (trong code là các điều kiện kiểm tra h[...] != h[...]).

Ta sẽ duyệt qua các giá trị $c_1$ và $c_2$ để cố gắng tìm ra một hình chữ nhật lớn nhất thoả mãn. Gọi $x_1 \lt x_2 \lt \ldots \lt x_s$ là các dòng có chứa chữ X nằm trong dải cột từ $c_1$ đến $c_2$. Khi này hình chữ nhật lớn nhất ta có thể tạo được sẽ có chiều rộng là $c_2 - c_1 + 1$ và chiều cao là:

max(x2 − 1, x3 − x1 − 1, x4 − x2 − 1, …, xs − xs−2 − 1, n − xs−1)

Nghĩa là: muốn chứa đúng chữ X ở dòng $x_t$ thì hình chữ nhật được kéo dài từ ngay dưới $x_{t-1}$ đến ngay trên $x_{t+1}$ (coi $x_0 = 0$ và $x_{s+1} = n + 1$ là 2 "lính canh" ở 2 đầu). Bài toán chuyển về việc duy trì độ cao này một cách hiệu quả.

Đến đây chúng ta có thể làm như sau: với mỗi giá trị $c_1$, ta sẽ duyệt $c_2$ giảm dần từ $m$. Ta sẽ coi mỗi giá trị $x_1, x_2, \ldots, x_s$ như một đỉnh trong danh sách liên kết (linked list) (mảng before, after trong code), và khi giảm $c_2$, các chữ X ở cột $c_2$ không còn nằm trong dải nữa nên ta xoá dần các đỉnh tương ứng và cập nhật lại kết quả: xoá 1 đỉnh thì chỉ có 2 đỉnh hàng xóm của nó bị thay đổi khoảng cách, nên chỉ cần tính lại chiều cao cho 2 đỉnh đó. Chiều cao lớn nhất chỉ có thể tăng lên khi xoá bớt đỉnh, nên ta chỉ cần lấy max. Việc dựng linked list ban đầu và việc xoá 1 node trong linked list có thể dễ dàng thực hiện được trong độ phức tạp lần lượt là $O(k)$ và $O(1)$. Độ phức tạp là $O(m^2 + km)$, vẫn chưa đủ nhanh để giải quyết bài toán này.

Nhận xét quan trọng
Ta chỉ cần quan tâm đến các giá trị $c_1$ thoả mãn một trong các điều kiện sau:
  • $c_1 = 1$;
  • Trong cột $c_1 - 1$ có ít nhất 1 chữ X.

Ta có thể dễ dàng chứng minh được: giả sử nếu tồn tại 1 hình chữ nhật tối ưu mà $c_1$ không thoả mãn điều kiện trên, tức là cột $c_1 - 1$ hoàn toàn trống, ta có thể giảm $c_1$ đi 1 và sẽ được 1 hình chữ nhật lớn hơn vẫn thoả mãn điều kiện đề bài (vì không thêm chữ X nào). Do chỉ có $k$ chữ X nên chỉ có tối đa $k + 1$ giá trị $c_1$ ta cần phải quan tâm (trong code là dòng if(i > 1 && contain[i-1].size() == 0) continue;). Từ đó ta có thể giảm độ phức tạp của thuật toán xuống $O(mk)$, đủ để giải quyết bài toán.

Em nhập kích thước bảng và các chữ X (mỗi chữ X là cặp "dòng cột") rồi bấm Tiếp để xem với từng dải cột $[c_1, c_2]$, chiều cao lớn nhất được tính từ các dòng có X như thế nào:

Ví dụ nhanh:
#include<bits/stdc++.h>
using namespace std;
int n, m, k;
vector<pair<int, int>> X(1005);

namespace sub3{
    int before[1005], after[1005], h[1005];
    vector<int> contain[10005];
    int solve(){
        sort(X.begin(), X.begin() + k);
        for(int i = 0; i < k; i++){
            contain[X[i].second].push_back(i+1);
            h[i+1] = X[i].first;
        }
        int ans = 0;
        h[0] = 0; h[k+1] = n+1;
        set<int> st;
        for(int i = m; i >= 1; i--){
            for(int j : contain[i]) st.insert(j);
            if(i > 1 && contain[i-1].size() == 0) continue;
            int last = 0, maximum = 0;
            for(int j : st){
                after[last] = j; before[j] = last;
                last = j;
            }
            before[k+1] = last; after[k+1] = k+1; after[last] = k+1;
            for(int j : st) if(h[after[j]] != h[j] && h[before[j]] != h[j]){
                maximum = max(maximum, h[after[j]] - h[before[j]] - 1);
            }
            for(int j = m; j >= i; j--){
                if(after[0] != k+1) ans = max(ans, maximum * (j-i+1));
                for(int j2 = 0; j2 < contain[j].size(); ){
                    int u = contain[j][j2], ub = before[u], ua = u;
                    int nj2 = j2;
                    int reua = ua;
                    while(nj2 < contain[j].size() && ua == contain[j][nj2]){
                        nj2++; ua = after[ua];
                    }
                    j2 = nj2;
                    before[ua] = ub; after[ub] = ua;
                    if(h[after[ua]] != h[ua] && h[before[ua]] != h[ua]){
                        maximum = max(maximum, h[after[ua]] - h[before[ua]] - 1);
                    }
                    if(h[after[ub]] != h[ub] && h[before[ub]] != h[ub]){
                        maximum = max(maximum, h[after[ub]] - h[before[ub]] - 1);
                    }
                }
            }
        }
        return ans;
    }
};

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> m >> k;
    for(int i = 0; i < k; i++) cin>>X[i].first>>X[i].second;
    cout << sub3::solve() << endl;
}
import sys
input = sys.stdin.readline

n, m, k = map(int, input().split())
X = [tuple(map(int, input().split())) for _ in range(k)]

class sub3:
    before = [0] * 1005
    after = [0] * 1005
    h = [0] * 1005
    contain = [[] for _ in range(10005)]

    @staticmethod
    def solve():
        X_sorted = sorted(X[:k])
        for i, (x, y) in enumerate(X_sorted):
            sub3.contain[y].append(i + 1)
            sub3.h[i + 1] = x

        ans = 0
        sub3.h[0] = 0
        sub3.h[k + 1] = n + 1
        st = set()

        for i in range(m, 0, -1):
            for j in sub3.contain[i]:
                st.add(j)
            if i > 1 and len(sub3.contain[i - 1]) == 0:
                continue

            last = 0
            maximum = 0
            for j in sorted(st):
                sub3.after[last] = j
                sub3.before[j] = last
                last = j

            sub3.before[k + 1] = last
            sub3.after[k + 1] = k + 1
            sub3.after[last] = k + 1

            for j in st:
                if sub3.h[sub3.after[j]] != sub3.h[j] and sub3.h[sub3.before[j]] != sub3.h[j]:
                    maximum = max(maximum, sub3.h[sub3.after[j]] - sub3.h[sub3.before[j]] - 1)

            for j in range(m, i - 1, -1):
                if sub3.after[0] != k + 1:
                    ans = max(ans, maximum * (j - i + 1))

                j2 = 0
                while j2 < len(sub3.contain[j]):
                    u = sub3.contain[j][j2]
                    ub = sub3.before[u]
                    ua = u
                    nj2 = j2
                    while nj2 < len(sub3.contain[j]) and ua == sub3.contain[j][nj2]:
                        nj2 += 1
                        ua = sub3.after[ua]
                    j2 = nj2
                    sub3.before[ua] = ub
                    sub3.after[ub] = ua
                    if sub3.h[sub3.after[ua]] != sub3.h[ua] and sub3.h[sub3.before[ua]] != sub3.h[ua]:
                        maximum = max(maximum, sub3.h[sub3.after[ua]] - sub3.h[sub3.before[ua]] - 1)
                    if sub3.h[sub3.after[ub]] != sub3.h[ub] and sub3.h[sub3.before[ub]] != sub3.h[ub]:
                        maximum = max(maximum, sub3.h[sub3.after[ub]] - sub3.h[sub3.before[ub]] - 1)

        return ans

print(sub3.solve())
C++▶ Chạy tay từng dòng codeSubtask 3 (AC): chỉ xét cột trái "đáng quan tâm", thu hẹp cột phải và xoá đỉnh khỏi danh sách liên kết – O(m·k).
Dữ liệu mẫu:
1#include<bits/stdc++.h>
2using namespace std;
3int n, m, k;
4vector<pair<int, int>> X(1005);
5
6namespace sub3{
7 int before[1005], after[1005], h[1005];
8 vector<int> contain[10005];
9 int solve(){
10 sort(X.begin(), X.begin() + k);
11 for(int i = 0; i < k; i++){
12 contain[X[i].second].push_back(i+1);
13 h[i+1] = X[i].first;
14 }
15 int ans = 0;
16 h[0] = 0; h[k+1] = n+1;
17 set<int> st;
18 for(int i = m; i >= 1; i--){
19 for(int j : contain[i]) st.insert(j);
20 if(i > 1 && contain[i-1].size() == 0) continue;
21 int last = 0, maximum = 0;
22 for(int j : st){
23 after[last] = j; before[j] = last;
24 last = j;
25 }
26 before[k+1] = last; after[k+1] = k+1; after[last] = k+1;
27 for(int j : st) if(h[after[j]] != h[j] && h[before[j]] != h[j]){
28 maximum = max(maximum, h[after[j]] - h[before[j]] - 1);
29 }
30 for(int j = m; j >= i; j--){
31 if(after[0] != k+1) ans = max(ans, maximum * (j-i+1));
32 for(int j2 = 0; j2 < contain[j].size(); ){
33 int u = contain[j][j2], ub = before[u], ua = u;
34 int nj2 = j2;
35 int reua = ua;
36 while(nj2 < contain[j].size() && ua == contain[j][nj2]){
37 nj2++; ua = after[ua];
38 }
39 j2 = nj2;
40 before[ua] = ub; after[ub] = ua;
41 if(h[after[ua]] != h[ua] && h[before[ua]] != h[ua]){
42 maximum = max(maximum, h[after[ua]] - h[before[ua]] - 1);
43 }
44 if(h[after[ub]] != h[ub] && h[before[ub]] != h[ub]){
45 maximum = max(maximum, h[after[ub]] - h[before[ub]] - 1);
46 }
47 }
48 }
49 }
50 return ans;
51 }
52};
53
54int main(){
55 ios::sync_with_stdio(0);
56 cin.tie(0);
57 cin >> n >> m >> k;
58 for(int i = 0; i < k; i++) cin>>X[i].first>>X[i].second;
59 cout << sub3::solve() << endl;
60}

Mình đã kiểm tra cả 3 bản code với 400 bảng ngẫu nhiên (có cả trường hợp nhiều chữ X cùng dòng, cùng cột) so với cách duyệt trâu, đều cho cùng kết quả. Với $N = M = 10^4$, $K = 1000$ thì bản C++ subtask 3 chỉ chạy khoảng 0.02 giây, bản Python khoảng 1.4 giây. Riêng bản Python của subtask 2 với $N = M = 500$ chạy mất gần 30 giây, nên nếu dùng Python thì các bạn nộp thẳng bản subtask 3 nhé.

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 😇