主席树可以查询静态区间第k大,实际上是一种特殊的可持久化线段树,所以首先来说一下什么是可持久化线段树。
具体来说,可持久化线段树是可以保存所有历史版本的线段树,每次插入,都会创建一颗新树,保存这次插入后版本的线段树,返回这个版本的线段树根节点,后面想查询一个历史版本时,就使用这个版本的根节点,进行线段树查询就行了,具体查询和线段树正常查询完全一样
关键是,如何保证创建历史版本这个操作的复杂度?如果暴力创建,每次都是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的,显然太慢了。但是结合动态开点线段树的知识,我们知道,每次修改一个点,实际上只会影响一条链上的元素,最多
O
(
l
o
g
n
)
O(logn)
O(logn)个点,线段树中剩余的点其实完全没有变化,那其实我们可以在每次插入时,只对受到影响的点创建新的版本,然后接到未受影响的点上,这样每次插入,创建新版本的复杂度就是
O
(
l
o
g
n
)
O(logn)
O(logn)的了,具体可以看这个图
接下来再来看主席树,实际上是可持久化线段树的一个应用,只是节点权值比较特殊,具体来说:一个节点对应的区间是
[
l
,
r
]
[l,r]
[l,r]的话,那么
c
n
t
cnt
cnt保存值在
[
l
,
r
]
[l,r]
[l,r]范围内的元素的个数。
看到这里,如果熟悉线段树二分的话,你应该可以猜到主席树是怎么查询区间第 k k k小的了。首先把每个元素插入主席树,都会产生一个版本,那么第 i i i个版本保存的就是下标在 [ 1 , i ] [1,i] [1,i]的所有元素,其次,下标在 [ 1 , i ] [1,i] [1,i]内,值在 [ l , r ] [l,r] [l,r]内的元素个数,是可以前缀和的,因此我们用 [ 1 , l − 1 ] , [ 1 , r ] [1,l-1],[1,r] [1,l−1],[1,r]两个版本的线段树的 c n t cnt cnt相减,就能得到下标在 [ l , r ] [l,r] [l,r]内,且值在某个范围内的的元素的个数,且这个个数随 [ 1 , i ] [1,i] [1,i]的 i i i的增加是不减的。
那么我们通过 [ 1 , r ] − [ 1 , l − 1 ] [1,r]-[1,l-1] [1,r]−[1,l−1]得到了下标在 [ l , r ] [l,r] [l,r]内元素的 c n t cnt cnt,实际上就是得到了这个范围内元素的一个权值线段树,然后我们要找第 k k k大,这其实就是个线段树二分的经典问题,我们不断检查左右子树的值域内的元素个数,如果 k k k小于左子树的元素个数,就进入左子树,否则进入右子树,知道来到叶节点,就是我们要找的元素的值
总结一下,主席树就是通过可持久化线段树的历史版本来解决区间查询,然后用线段树二分,在权值线段树上找到第 k k k大的位置
具体仍然可以看下图,这里演示的是插入
[
4
,
1
,
1
,
2
]
[4,1,1,2]
[4,1,1,2]四个元素的情况
最后是板子,这里下标是从0开始的
class PersistentSegmentTree {
private:
struct Node {
Node *left, *right;
int cnt;
Node() : left(nullptr), right(nullptr), cnt(0) {}
Node(Node* l, Node* r, int c) : left(l), right(r), cnt(c) {}
};
vector<Node*> roots;
int n;
Node* build(int l, int r) {
Node* node = new Node();
if (l == r) return node;
int mid = (l + r) >> 1;
node->left = build(l, mid);
node->right = build(mid + 1, r);
return node;
}
Node* update(Node* prev, int l, int r, int pos) {
Node* curr = new Node(prev->left, prev->right, prev->cnt + 1);
if (l == r) return curr;
int mid = (l + r) >> 1;
if (pos <= mid)
curr->left = update(prev->left, l, mid, pos);
else
curr->right = update(prev->right, mid + 1, r, pos);
return curr;
}
int query(Node* root1, Node* root2, int l, int r, int k) {
if (l == r) return l;
int mid = (l + r) >> 1;
int leftCount = root2->left->cnt - root1->left->cnt;
if (k <= leftCount)
return query(root1->left, root2->left, l, mid, k);
else
return query(root1->right, root2->right, mid + 1, r, k - leftCount);
}
public:
// 使用数组初始化可持久化线段树
PersistentSegmentTree(vector<int>& arr) {
n = arr.size();
if (n == 0) return;
// 离散化
vector<int> sorted = arr;
sort(sorted.begin(), sorted.end());
sorted.erase(unique(sorted.begin(), sorted.end()), sorted.end());
// 建立映射关系
unordered_map<int, int> mapping;
for (int i = 0; i < sorted.size(); i++) {
mapping[sorted[i]] = i + 1;
}
// 构建所有版本的线段树
roots.push_back(build(1, sorted.size()));
for (int i = 0; i < n; i++) {
roots.push_back(update(roots.back(), 1, sorted.size(), mapping[arr[i]]));
}
// 保存离散化后的数组,用于还原查询结果
sorted.insert(sorted.begin(), 0); // 添加一个哨兵,使下标从1开始
this->sorted = sorted;
}
// 查询区间[l,r]内的第k大值,l和r从0开始
int queryKth(int l, int r, int k) {
if (l < 0 || r >= n || k <= 0 || k > r - l + 1)
return -1; // 无效查询
return sorted[query(roots[l], roots[r + 1], 1, sorted.size() - 1, k)];
}
private:
vector<int> sorted; // 用于还原离散化的值
};
void solve(void){
cin>>n>>m;
vi a(n);
rep(i,0,n-1)cin>>a[i];
PersistentSegmentTree tr(a);
rep(i,1,m){
int l,r,k;
cin>>l>>r>>k;
l--,r--;
cout<<tr.queryKth(l,r,k)<<'\n';
}
}