Hình vuông con
Đề bài
Bài 5. Hình vuông con
Cho lưới ô vuông $A$ kích thước $N \times N$. Các dòng được đánh số $1$ đến $N$ từ trên xuống dưới, các cột được đánh số $1$ đến $N$ từ trái qua phải. Ô nằm trên giao giữa dòng $i$ và cột $j$ được gọi là ô $(i, j)$ và trên đó có ghi một số nguyên dương $A_{ij}$ $(1 \le i, j \le N)$.
Yêu cầu: hãy lập trình chọn một ô vuông con có kích thước $K \times K$ có tổng giá trị của tất cả các ô của hình vuông con là lớn nhất.
Input: đọc từ file HV.INP:
- Dòng đầu tiên chứa hai số nguyên dương $N, K$ $(N \le 10^3, K \le N)$.
- Dòng thứ $i$ trong số $N$ dòng tiếp theo chứa $N$ số nguyên dương, số thứ $j$ là $A_{ij}$ $(A_{ij} \le 10^3)$.
Output: ghi ra file HV.OUT gồm một dòng duy nhất ghi một số nguyên dương là tổng giá trị lớn nhất theo yêu cầu đề ra.
4 3
1 9 1 1
9 9 9 9
1 9 9 9
1 9 9 1486Giải thích ví dụ: hình vuông con $3 \times 3$ ở góc dưới phải (dòng 2–4, cột 2–4) có tổng $9 \cdot 8 + 14 = 86$, lớn nhất trong 4 hình vuông con $3 \times 3$ của bảng.
Lưu ý trên CLUE OJ: bài đọc file HV.INP, ghi file HV.OUT. Code bên dưới dùng mẹo if (fopen("HV.INP", "r")): có file thì đọc/ghi file, không có thì đọc bàn phím – in màn hình.
Lời mở đầu
Đây là bài "kinh điển" nhất để học tổng tiền tố 2 chiều: tính tổng của rất nhiều hình vuông con trên một bảng số. Mình đi từ cách vét cạn cộng từng ô (ăn được các test nhỏ) rồi nâng lên cách tiền tố 2 chiều đạt điểm tối đa. Bạn nào đã làm bài Chọn quà cùng chương sẽ thấy hai bài là "anh em ruột".
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 OJLộ 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ó 100 test. Đề không ghi subtask, nhưng bộ test chia rõ 2 mức: 30 test đầu có $n \le 10$, 70 test sau có $n$ từ 131 tới 1000 (20 test cuối đều $n = 1000$). Mọi số trong bảng đều là số nguyên dương từ 1 đến 999.
| Test | n | k | Giá trị ô | Đáp án | Ghi chú |
|---|---|---|---|---|---|
| 1 – 30 | 4 – 10 | 2 – 8 | 1 – 20 | 61 – 719 | Nhỏ, kiểm tra tay được |
| 31 – 80 | 131 – 999 | 1 – 809 | 1 – 999 | 999 – 133343221 | Ngẫu nhiên, n tới 1000 |
| 81 – 100 | 1000 | 2 – 968 | 1 – 999 | 3789 – 190011046 | Lớn nhất: n = 1000 |
| Mức | Điều kiện | Test | Cách đạt |
|---|---|---|---|
| Nhỏ | $n \le 10$ | 1 – 30 | Cách 1, Cách 2 |
| Lớn | $131 \le n \le 1000$ | 31 – 100 | Cách 2 (cách 1 chỉ qua khi $k$ nhỏ hoặc gần $n$) |
Đáp án lớn nhất là $190011046$ (test 83, $n = 1000$, $k = 968$) – vẫn vừa kiểu int, nhưng nếu đề cho số lớn hơn thì tổng có thể vượt $2^{31}$, nên mình dùng long long. Có cả test $k = 1$ (đáp án là số lớn nhất của bảng) và test $k = n$ (chỉ có 1 hình vuông là cả bảng).
Ý tưởngTrượt khung k × k qua mọi vị trí
Một hình vuông con $k \times k$ được xác định bởi góc trên trái $(i, j)$ với $1 \le i, j \le n - k + 1$, nên có tất cả $(n - k + 1)^2$ hình vuông con. Ta chỉ việc tính tổng từng hình và lấy giá trị lớn nhất. Câu hỏi duy nhất là tính tổng một hình vuông nhanh tới mức nào.
Em sửa bảng số, chọn $k$ rồi bấm Tiếp để xem khung $k \times k$ trượt qua bảng. Bên dưới là bảng tiền tố $s$ với 4 ô dùng để tính nhanh tổng của khung đang xét (xanh mang dấu +, đỏ mang dấu −):
Cách 1Vét cạn: cộng từng ô – O(n²·k²)
Với mỗi góc trên trái $(i, j)$, dùng 2 vòng lặp nữa cộng $k^2$ ô của hình vuông.
- Điều kiện
i + k - 1 <= nđảm bảo hàng cuối của hình vuông (hàng $i + k - 1$) vẫn nằm trong bảng; tương tự cho cột. - Tổng lớn nhất có thể tới $n^2 \cdot \max a$ nên mình dùng
long longcho biến tổng (với bộ test này đáp án lớn nhất khoảng $1{,}9 \cdot 10^8$ vẫn vừaint, nhưng dùnglong longcho chắc).
Số phép cộng là $(n - k + 1)^2 \cdot k^2$, lớn nhất khi $k \approx n/2$: với $n = 1000$ là khoảng $500^2 \cdot 500^2 = 6{,}25 \cdot 10^{10}$ – quá chậm. Nhưng khi $k$ rất nhỏ hoặc $k$ gần bằng $n$ thì số phép tính lại ít.
Cách 2Tổng tiền tố 2 chiều – O(n²)
Gọi $s[i][j]$ là tổng các số trong hình chữ nhật từ ô $(1, 1)$ đến ô $(i, j)$. Bảng này tính ngay khi đọc:
Lần này mình chọn $(i, j)$ là góc dưới phải của hình vuông con (với $k \le i, j \le n$) cho công thức gọn hơn: hình vuông gồm các hàng $i - k + 1 \dots i$, các cột $j - k + 1 \dots j$, và
- Không cần lưu mảng
a: đọc số nào thì cộng luôn vàos[i][j]. - Mảng
skiểulong longkhai báo toàn cục (khoảng 8 MB), hàng 0 và cột 0 bằng 0 làm "lề" cho công thức. - Bản Python: mỗi hàng của $s$ = hàng trên cộng tổng dồn của hàng hiện tại; khi tính đáp án, với mỗi $i$ lấy hiệu 2 hàng
d = s[i] - s[i - k]rồimax(d[j] - d[j - k])bằngmapcho nhanh.
Tổng kếtSo sánh 2 cách
| Cách | Độ phức tạp | C++ | Python | Mức đạt |
|---|---|---|---|---|
| 1. Vét cạn | $O(n^2 k^2)$ | 84/100 | 50/100 | Test nhỏ + test lớn có k nhỏ |
| 2. Tiền tố 2D | $O(n^2)$ | 100/100 | 100/100 | Toàn bộ |
Nhận xét
Đăng nhận xét