Hướng dẫn giải của Tìm số nhỏ thứ K trong mảng
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.
Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.
Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.
Bạn phải đăng nhập để mở lời giải này.
Đăng nhậpTác giả: , ,
Editorial for aquery2: Tìm số nhỏ thứ K trong mảng
Hiểu bài toán
Bài toán yêu cầu tìm số nhỏ thứ K trong một dãy số được tạo ra bằng cách thực hiện Q thao tác. Mỗi thao tác chèn bi số có giá trị ai vào cuối dãy. Dãy số này có thể rất lớn (tổng b_i lên tới 10^14), nên không thể lưu trữ trực tiếp. Ta chỉ cần tìm giá trị của phần tử thứ K khi sắp xếp dãy số tăng dần.
Các cách tiếp cận
Cách Brute Force Simulation
#include <stdio.h>
#include <stdlib.h>
int cmpf(const void *a, const void *b) {
int int_a = *(int*)a;
int int_b = *(int*)b;
if (int_a < int_b) return -1;
if (int_a > int_b) return 1;
return 0;
}
int main() {
// Kích thước mảng cố định, dễ bị tràn bộ nhớ hoặc sai giới hạn
int arr[1000001];
int temp1, temp2;
int pos;
int n, i, j, cur = 0;
scanf("%d", &n);
for(i = 0; i < n; i++){
scanf("%d %d", &temp1, &temp2);
for(j = 0; j < temp2; j++){
arr[cur] = temp1;
cur++;
}
}
scanf("%d", &pos);
qsort(arr, cur, sizeof(int), cmpf);
printf("%d", arr[pos-1]);
return 0;
}
- Time Complexity: O(TotalElements * log TotalElements)
- Space Complexity: O(Total_Elements)
Approach này tạo ra toàn bộ dãy số và sắp xếp nó. Điều này chỉ hiệu quả nếu tổng số lượng phần tử (tổng của các bi) nhỏ. Với ràng buộc của bài toán (tổng bi có thể lên tới 10^14), approach này sẽ tràn bộ nhớ và quá thời gian xử lý.
Cách Binary Search on Answer
#include <bits/stdc++.h>
using namespace std;
struct Op {
long long a, b;
};
vector<Op> ops;
long long K;
int Q;
// Tính số lượng phần tử <= val trong dãy
long long count_le(long long val) {
long long cnt = 0;
for (const auto& op : ops) {
if (op.a <= val) {
cnt += op.b;
}
}
return cnt;
}
int main() {
cin >> Q;
ops.resize(Q);
long long total_b = 0;
for (int i = 0; i < Q; i++) {
cin >> ops[i].a >> ops[i].b;
total_b += ops[i].b;
}
cin >> K;
// Binary Search tìm giá trị x sao cho số lượng phần tử <= x >= K
// Giá trị a_i nằm trong [1, 10^9]
long long left = 1, right = 1e9 + 7;
long long ans = right;
while (left <= right) {
long long mid = left + (right - left) / 2;
if (count_le(mid) >= K) {
ans = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
cout << ans << endl;
return 0;
}
- Time Complexity: O(Q * log(10^9))
- Space Complexity: O(Q)
Ta sử dụng kỹ thuật Binary Search trên không gian giá trị (từ 1 đến 10^9). Với mỗi giá trị 'mid', ta đếm xem có bao nhiêu số trong dãy nhỏ hơn hoặc bằng 'mid'. Nếu số lượng này >= K, thì giá trị nhỏ thứ K phải nằm ở nửa bên trái (bao gồm mid). Ngược lại, nó nằm ở nửa phải. Hàm đếm chỉ cần duyệt qua Q thao tác, rất nhanh.
Cách Sorting and Counting (Optimal)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int Q;
cin >> Q;
vector<pair<long long, long long>> ops(Q);
for (int i = 0; i < Q; i++) {
cin >> ops[i].first >> ops[i].second;
}
long long K;
cin >> K;
// Sắp xếp các cặp (a_i, b_i) theo giá trị a_i tăng dần
sort(ops.begin(), ops.end());
// Duyệt qua các thao tác đã sắp xếp
for (int i = 0; i < Q; i++) {
// Nếu K nhỏ hơn hoặc bằng số lượng phần tử trong thao tác hiện tại,
// thì số nhỏ thứ K chính là giá trị a_i của thao tác này.
if (K <= ops[i].second) {
cout << ops[i].first << endl;
return 0;
}
// Ngược lại, trừ đi số lượng phần tử của thao tác này và tiếp tục
K -= ops[i].second;
}
return 0;
}
- Time Complexity: O(Q log Q)
- Space Complexity: O(Q)
Đây là approach tối ưu nhất. Thay vì binary search, ta nhận thấy rằng nếu sắp xếp các thao tác theo giá trị ai tăng dần, thì các số trong dãy cũng sẽ xuất hiện theo thứ tự giá trị tăng dần (từ các thao tác có ai nhỏ nhất). Ta chỉ cần duyệt qua danh sách đã sắp xếp và trừ dần K cho đến khi K nhỏ hơn hoặc bằng bi của thao tác hiện tại. Khi đó, ai chính là đáp án.
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(TotalElements * log TotalElements) | O(Total_Elements) | Brute Force Simulation |
| 2 | O(Q * log(10^9)) | O(Q) | Binary Search on Answer |
| 3 | O(Q log Q) | O(Q) | Sorting and Counting (Optimal) |
Bài học kinh nghiệm
- Bài toán không cần xây dựng toàn bộ mảng, chỉ cần tìm giá trị tại vị trí K.
- Số lượng thao tác Q (10^5) nhỏ hơn nhiều so với tổng số phần tử (10^14), nên thuật toán cần dựa vào Q chứ không dựa vào tổng số phần tử.
- Nếu sắp xếp các thao tác theo giá trị a_i, ta có thể mô phỏng quá trình sắp xếp toàn bộ mảng một cách gián tiếp.
Lỗi thường gặp
- Sử dụng biến kiểu int để lưu tổng số phần tử hoặc K, gây tràn số (overflow) vì các giá trị này có thể lên tới 10^14.
- Lập trình mô phỏng trực tiếp (chèn từng phần tử vào vector/mảng) gây quá thời gian hoặc tràn bộ nhớ.
- Quên trường hợp K nằm trong chính khối lượng phần tử của thao tác đầu tiên sau khi sắp xếp.
Bình luận