Hướng dẫn giải của Bài 8: Hợp nhất các dãy số (MERGEVEC)
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 ~K~ dãy số nguyên, mỗi dãy có độ dài ~L_i~ bất kỳ. Hãy gom toàn bộ các dãy này thành một vector duy nhất, loại bỏ tất cả các phần tử trùng lặp và sắp xếp theo thứ tự tăng dần.
- Dữ liệu vào (Input):
- Dòng 1: Số nguyên ~K~ (~1 \le K \le 10^4~).
- ~K~ cụm dòng tiếp theo: Mỗi cụm dòng gồm 2 dòng:
- Dòng đầu chứa số nguyên ~L_i~ (số lượng phần tử của dãy thứ ~i~).
- Dòng sau chứa ~L_i~ số nguyên của dãy đó.
- Giới hạn: Tổng số lượng phần tử của tất cả các dãy ~\sum L_i \le 2 \times 10^5~.
- Dữ liệu ra (Output):
- Dòng 1: Một số nguyên duy nhất là số lượng phần tử độc nhất (không trùng lặp).
- Dòng 2: Dãy số sau khi đã sắp xếp tăng dần và loại bỏ trùng lặp, các số cách nhau bởi một khoảng trắng.
2. Phân tích bài toán & Giới hạn
Giới hạn bài toán:
- Số lượng dãy: ~K \le 10^4~.
- Tổng số phần tử cần xử lý: ~M = \sum L_i \le 2 \times 10^5~.
- Giá trị các số nguyên nằm trong phạm vi kiểu số nguyên 32-bit (
int).
Đánh giá thuật toán & Cấu trúc dữ liệu:
- Có hai hướng xử lý phổ biến:
- Cách 1 (Dùng
std::vectorkết hợpsortvàunique): Đọc tất cả ~M~ phần tử đưa vào một vector, sau đó dùngsortđể sắp xếp tăng dần, tiếp theo dùng hàmuniquekèm lệnheraseđể lọc bỏ các phần tử trùng nhau.- Độ phức tạp thời gian: ~\mathcal{O}(M \log M) \approx 2 \times 10^5 \times 18 \approx 3.6 \times 10^6~ phép tính. Chạy cực nhanh (~< 0.05~ giây).
- Cách 2 (Lọc thủ công sau khi
sort): Sau khi sắp xếp vector tăng dần, ta duyệt từ đầu đến cuối và chỉ gom các phần tử khác với phần tử đứng liền trước nó.
- Cách 1 (Dùng
- Cả hai cách đều tối ưu tuyệt đối về thời gian và bộ nhớ. Cách dùng bộ đôi
std::uniquevàerasetrên vector là phương pháp chuẩn mực, đúng theo yêu cầu dùng vector của đề bài.
- Có hai hướng xử lý phổ biến:
3. Thuật toán xử lý
Cấu trúc lưu trữ:
- Khai báo một mảng động:
vector<int> a;.
- Khai báo một mảng động:
Các bước thực hiện:
- Khai báo biến ~k~. Nhập số lượng dãy bằng lệnh chuẩn:
cin >> k;. - Dùng vòng lặp ~i~ chạy từ ~1~ đến ~k~:
- Nhập số phần tử của dãy hiện tại:
cin >> n;(với ~n = L_i~). - Dùng vòng lặp ~j~ chạy từ ~1~ đến ~n~: đọc từng giá trị ~x~ và đẩy trực tiếp vào cuối vector bằng
a.push_back(x);.
- Nhập số phần tử của dãy hiện tại:
- Sắp xếp toàn bộ vector ~a~ tăng dần:
sort(a.begin(), a.end());
- Loại bỏ các phần tử trùng lặp liền kề nhau:
- Sử dụng câu lệnh kết hợp chuẩn trong C++:
a.erase(unique(a.begin(), a.end()), a.end()); - Giải thích: Hàm
uniquesẽ dồn các phần tử độc nhất về nửa đầu vector và trả về con trỏ (iterator) tại vị trí phần tử rác đầu tiên. Hàmerasesẽ xóa toàn bộ các phần tử dư thừa từ vị trí đó đến hết vector.
- Sử dụng câu lệnh kết hợp chuẩn trong C++:
- In kết quả:
- Dòng 1: In số lượng phần tử còn lại bằng
a.size()và xuống dòng bằngcout << endl;. - Dòng 2: Duyệt từng phần tử trong vector
atừ chỉ số ~0~ đếna.size() - 1để in ra các giá trị cách nhau dấu cách, kết thúc bằngcout << endl;.
- Dòng 1: In số lượng phần tử còn lại bằng
- Khai báo biến ~k~. Nhập số lượng dãy bằng lệnh chuẩn:
Độ phức tạp:
- Thời gian: ~\mathcal{O}(M \log M)~ với ~M = \sum L_i \le 2 \times 10^5~, thời gian thực thi ~< 0.05~ giây.
- Không gian (bộ nhớ): ~\mathcal{O}(M)~ — Bộ nhớ tiêu tốn khoảng vài MB, hoàn toàn an toàn trong giới hạn bộ nhớ tiêu chuẩn (256 MB).
4. Mã nguồn cài đặt (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
// Tối ưu tốc độ vào/ra chuẩn
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int k;
cin >> k;
vector<int> a;
// Đọc k cụm dữ liệu
for (int i = 1; i <= k; i++) {
int n;
cin >> n;
for (int j = 1; j <= n; j++) {
int x;
cin >> x;
a.push_back(x);
}
}
// Sắp xếp các phần tử tăng dần
sort(a.begin(), a.end());
// Loại bỏ các phần tử trùng lặp liên tiếp
a.erase(unique(a.begin(), a.end()), a.end());
// In số lượng phần tử độc nhất
cout << a.size() << endl;
// In các phần tử đã sắp xếp và lọc trùng
for (int i = 0; i < (int)a.size(); i++) {
cout << a[i] << " ";
}
cout << endl;
return 0;
}
Đang tải...