今天下午学会了主席树,写写自己的理解。
- 主席树,也叫可持久化权值线段树。
- 什么叫权值线段树?就是线段树的叶子结点保存的是当前值的个数。
- 什么叫可持久化?可持久化就是可以保留以前建树的历史版本,比如你要对n个元素建树,那么在可持久化线段树中就会有n颗树,其根节点为root[i]。root[i]代表第i颗线段树,这颗线段树由 1 ~ i 的所有元素组成。
- 在线段树中,每次新插入一个值,最多会使logn个点的也就是一条链上的所有节点的值发生改变。
- 主席树就是基于这一特点,每次插入就会新增这些值要改变的logn个点,而其余部分则可以和以前历史版本的线段树共用。
- 与普通线段树不同的是,线段树结点编号是固定的,而可持久化线段树给结点编号是按访问节点的先后顺序。
接下来就贴一个板子题,求区间第k大。
题解就写在这了,我们可以先想一下普通的权值线段树求1~n区间的第k大,是不是就是树上二分,如果我左儿子保存的区间值的个数比k大,那区间第k大一定在左区间,否则再到右区间查询第(k-左区间权值个数)大。
而在主席树,如果要查询 L ~ R 区间第 K 大 , 我们就可以用第R颗线段树减去第L-1颗线段树,因为每个结点的权值其实就是一个前缀和,减掉之和就相当于对L 到 R 区间建了一颗线段树,所以查询操作和普通的权值线段树一样。具体可以看代码加深理解。
题意:给你一个序列,让你求l~r区间第k大的元素。
题解:主席树。
#include<bits/stdc++.h>
using namespace std;
#define mid (l + r) / 2
typedef long long ll;
const int maxn = 1e5+10,N=1e9;
int rt[maxn],ls[maxn*18],rs[maxn*18],sum[maxn*18],cnt;
int a[maxn],b[maxn];
void up(int &o, int pre, int l,int r, int k){
o = ++cnt;
sum[o] = sum[pre] + 1;
ls[o] = ls[pre];
rs[o] = rs[pre];
if(l==r) return;
if(k <= mid)
up(ls[o], ls[pre], l, mid, k);
else
up(rs[o], rs[pre], mid + 1, r, k);
}
int qu(int o, int pre, int l, int r, int k){
if(l == r)
return b[l];
if(sum[ls[o]] - sum[ls[pre]] >= k)
return qu(ls[o], ls[pre], l, mid, k);
return qu(rs[o], rs[pre], mid + 1, r, k - (sum[ls[o]] - sum[ls[pre]]));
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
int n,m,x,l,r,k;
cnt=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]),b[i]=a[i];
sort(b + 1,b + 1 + n);
int sz = unique(b + 1, b + 1 + n) - b - 1;
for(int i=1;i<=n;++i){
x = lower_bound(b + 1, b + 1 + sz, a[i]) - b;
up(rt[i], rt[i - 1], 1, sz, x);
}
while(m--){
scanf("%d%d%d", &l, &r, &k);
printf("%d\n",qu(rt[r], rt[l - 1], 1, sz, k));
}
}
}