Hướng giải của Tìm Phần Tử Nhỏ Thứ K
Nhớ rằng hướng dẫn giải này chỉ nên sử dụng khi bế tắc, và tuyệt đối không nên sao chép mã nguồn kèm theo. Hãy tôn trọng tác giả bài tập và người viết hướng dẫn giải.
Nộp mã nguồn lời giải chính thức trước khi giải bài tập đó có thể khiến bạn bị ban.
Nộp mã nguồn lời giải chính thức trước khi giải bài tập đó có thể khiến bạn bị ban.
Tác giả:
Hướng dẫn giải
Bài toán tìm phần tử nhỏ thứ \(k\) trong khoảng \([L, R]\). Chúng ta giải quyết bằng Persistent Segment Tree (Cây phân đoạn bền vững) kết hợp với Rời rạc hóa dữ liệu:
- Rời rạc hóa các giá trị của mảng \(A\) thành các chỉ số từ \(0\) đến \(M-1\) (\(M \le N\)).
- Cây phân đoạn bền vững sẽ quản lý số lượng phần tử thuộc các khoảng giá trị sau rời rạc hóa.
- Khi duyệt từ đầu đến cuối mảng, ta chèn phần tử \(A[i]\) vào cây và tạo ra một phiên bản cây lịch sử mới (roots tương ứng phiên bản \(i\)).
- Để tìm số nhỏ thứ \(k\) trong khoảng \([L, R]\), ta thực hiện đi đồng thời trên cả hai phiên bản cây lịch sử \(L-1\) và \(R\):
- Tính số lượng số nằm bên nhánh con trái:
count = tree[root_R.left].count - tree[root_L-1.left].count. - Nếu
count >= k: Số nhỏ thứ \(k\) chắc chắn nằm bên nhánh trái \(\to\) ta rẽ trái trên cả hai cây. - Ngược lại: Số đó nằm bên nhánh phải \(\to\) rẽ phải và giảm \(k\) đi lượng
count: \(k \leftarrow k - count\).
- Tính số lượng số nằm bên nhánh con trái:
Độ phức tạp thời gian và không gian: \(O((N + Q) \log N)\).
Mã nguồn C++ mẫu
#include <bits/stdc++.h>
using namespace std;
struct Node {
int count; int left, right;
};
class PersistentSegTree {
public:
vector<Node> tree;
vector<int> roots;
int n;
PersistentSegTree(int size) { n = size; roots.push_back(build(0, n-1)); }
int build(int start, int end) {
int id = tree.size();
tree.push_back({0, -1, -1});
if(start == end) return id;
int mid = (start + end)/2;
int l = build(start, mid);
int r = build(mid+1, end);
tree[id].left = l; tree[id].right = r;
return id;
}
int update(int prev_id, int start, int end, int pos) {
int id = tree.size();
tree.push_back(tree[prev_id]);
tree[id].count++;
if(start == end) return id;
int mid = (start + end)/2;
if(pos <= mid) tree[id].left = update(tree[prev_id].left, start, mid, pos);
else tree[id].right = update(tree[prev_id].right, mid+1, end, pos);
return id;
}
int query(int left_id, int right_id, int start, int end, int k) {
if(start == end) return start;
int count = tree[tree[right_id].left].count - tree[tree[left_id].left].count;
int mid = (start + end)/2;
if(count >= k) return query(tree[left_id].left, tree[right_id].left, start, mid, k);
return query(tree[left_id].right, tree[right_id].right, mid+1, end, k - count);
}
};
int main(){
ios_base::sync_with_stdio(false); cin.tie(NULL);
int n, q; if(!(cin >> n >> q)) return 0;
vector<int> a(n);
vector<int> temp(n);
for(int i=0;i<n;i++) { cin >> a[i]; temp[i] = a[i]; }
sort(temp.begin(), temp.end());
temp.erase(unique(temp.begin(), temp.end()), temp.end());
int m = temp.size();
PersistentSegTree pst(m);
for(int i=0;i<n;i++){
int pos = lower_bound(temp.begin(), temp.end(), a[i]) - temp.begin();
int new_root = pst.update(pst.roots.back(), 0, m-1, pos);
pst.roots.push_back(new_root);
}
while(q--){
int l, r, k; cin >> l >> r >> k;
int ans_idx = pst.query(pst.roots[l-1], pst.roots[r], 0, m-1, k);
cout << temp[ans_idx] << "\n";
}
}
Nhận xét