Hướng dẫn giải của Bài 6: Ma trận răng cưa động (JAGGED.CPP)
Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.
1. Tóm tắt đề bài
Cho ~N~ danh sách số nguyên. Dòng thứ ~i~ bắt đầu bằng số nguyên ~K_i~ biểu thị số lượng phần tử của dòng đó, theo sau là ~K_i~ số nguyên. Có ~Q~ truy vấn, mỗi truy vấn gồm hai số ~r~ và ~c~: hãy in ra giá trị ở hàng ~r~, cột thứ ~c~ (chỉ số hàng và cột tính từ 1). Nếu vị trí đó không tồn tại (~c > K_r~), in ra -1.
Dữ liệu vào (Input):
- Dòng 1: Hai số nguyên ~N, Q~ (~N, Q \le 2 \times 10^5~).
- ~N~ dòng tiếp theo: Dòng thứ ~i~ ghi số ~K_i~ và theo sau là ~K_i~ số nguyên (~\sum K_i \le 5 \times 10^5~).
- ~Q~ dòng tiếp theo: Mỗi dòng gồm 2 số nguyên ~r, c~.
Dữ liệu ra (Output):
- Gồm ~Q~ dòng tương ứng với kết quả của mỗi truy vấn.
2. Phân tích bài toán & Giới hạn
Giới hạn bài toán:
- ~N, Q \le 2 \times 10^5~.
- Tổng số phần tử của toàn bộ ma trận: ~\sum K_i \le 5 \times 10^5~.
- Số lượng truy vấn ~Q~ lớn (~2 \times 10^5~), đòi hỏi mỗi truy vấn phải trả lời ngay trong thời gian ~\mathcal{O}(1)~.
Bẫy bộ nhớ khi dùng mảng hai chiều tĩnh:
- Nếu khai báo mảng tĩnh dạng
int a[200005][200005], chương trình sẽ tốn ~\approx 2 \times 10^5 \times 2 \times 10^5 \times 4~ bytes ~\approx 160.000\text{ MB} = 160\text{ GB}~ bộ nhớ ~\rightarrow~ Tràn bộ nhớ ngay lập tức (Memory Limit Exceeded). - Mặc dù số hàng ~N~ có thể lớn, nhưng tổng số phần tử thực tế chỉ có ~\sum K_i \le 5 \times 10^5~. Đây chính là dạng ma trận răng cưa (mỗi hàng có độ dài khác nhau).
- Giải pháp tối ưu: Dùng mảng động các vector (
vector<int> a[N + 1]), mỗi hàng chỉ cấp phát đúng số phần tử mà nó chứa.
- Nếu khai báo mảng tĩnh dạng
Tối ưu vào/ra:
- Do số lượng dữ liệu vào/ra lên tới hàng trăm nghìn số (~N, Q \le 2 \times 10^5~), bắt buộc phải mở tốc độ nhập xuất chuẩn bằng
cin.tie(NULL);.
- Do số lượng dữ liệu vào/ra lên tới hàng trăm nghìn số (~N, Q \le 2 \times 10^5~), bắt buộc phải mở tốc độ nhập xuất chuẩn bằng
3. Thuật toán xử lý
Cấu trúc lưu trữ:
- Khai báo một mảng các vector số nguyên toàn cục:
vector<int> bang[NMAX]. - Hàng thứ ~i~ sẽ lưu các phần tử theo thứ tự nhập vào. Vì vector trong C++ đánh số chỉ số từ
0, ta có thể:- Hoặc đẩy thêm một phần tử giả
0vào đầu mỗi hàng để chỉ số cột bắt đầu từ1. - Hoặc truy xuất cột ~c~ tại vị trí
c - 1.
- Hoặc đẩy thêm một phần tử giả
- Khai báo một mảng các vector số nguyên toàn cục:
Các bước thực hiện:
- Nhập ~N~ và ~Q~.
- Với mỗi dòng ~i~ từ ~1~ đến ~N~:
- Nhập số lượng phần tử ~K_i~ của dòng đó.
- Cho một vòng lặp chạy ~K_i~ lần: đọc từng giá trị và dùng lệnh
push_backđể thêm vàobang[i].
- Lần lượt xử lý ~Q~ truy vấn:
- Đọc tọa độ hàng ~r~ và cột ~c~.
- Kiểm tra tính hợp lệ của vị trí:
- Nếu ~c > bang[r].size()~ (cột vượt quá số lượng phần tử thực có của hàng ~r~): in ra
-1. - Ngược lại: in ra phần tử
bang[r][c - 1].
- Nếu ~c > bang[r].size()~ (cột vượt quá số lượng phần tử thực có của hàng ~r~): in ra
- Xuống dòng bằng
cout << endl;.
Độ phức tạp:
- Thời gian:
- Nhập dữ liệu ma trận: ~\mathcal{O}(N + \sum K_i)~.
- Trả lời mỗi truy vấn: ~\mathcal{O}(1)~.
- Tổng thời gian: ~\mathcal{O}(N + \sum K_i + Q) \approx 10^6~ phép tính, chạy cực nhanh trong thời gian ~< 0.1~ giây.
- Không gian (bộ nhớ): ~\mathcal{O}(N + \sum K_i)~, tiêu tốn chưa đến ~15\text{ MB}~, hoàn toàn tối ưu.
- Thời gian:
4. Mã nguồn cài đặt (C++)
#include <bits/stdc++.h>
using namespace std;
const int NM = 200005;
// Mảng vector lưu ma trận răng cưa động
vector<int> a[NM];
int main() {
// Tối ưu tốc độ nhập/xuất chuẩn
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, q;
cin >> n >> q;
// Nhập dữ liệu cho từng hàng của ma trận
for (int i = 1; i <= n; i++)
{
int k;
cin >> k;
for (int j = 1; j <= k; j++)
{
int x;
cin >> x;
a[i].push_back(x);
}
}
// Xử lý lần lượt từng truy vấn
for (int i = 1; i <= q; i++)
{
int r, c;
cin >> r >> c;
// Kiểm tra xem vị trí cột c có nằm trong phạm vi số phần tử của hàng r không
if (c > a[r].size())
{
cout << -1 << endl;
} else
{
// Do vector đánh chỉ số từ 0 nên cột thứ c nằm ở vị trí c - 1
cout << a[r][c - 1] << endl;
}
}
return 0;
}
Đang tải...