主席树
主席树的主体是线段树,准确的说,是很多棵线段树,存的是一段数字区间出现次数(所以要先离散化可能出现的数字)。举个例子,假设我每次都要求整个序列内的第 k 小,那么对整个序列构造一个线段树,然后在线段树上不断找第 k 小在当前数字区间的左半部分还是右半部分。这个操作和平衡树的 Rank 操作一样,只是这里将离散的数字搞成了连续的数字。
先假设没有修改操作:
对于每个前缀 S1…i,保存这样一个线段树 Ti,组成主席树。这样是会 MLE 的,所以我们用到了一点优化。
注意,这个线段树对一条线段,保存的是这个数字区间的出现次数,所以是可以互相加减的!还有,由于每棵线段树都要保存同样的数字,所以它们的大小、形态也都是一样的!这实在是两个非常好的性质,是平衡树所不具备的。
对于询问 (i,j),我只要拿出 Tj 和 Ti-1,对每个节点相减就可以了。说的通俗一点,询问 i..j 区间中,一个数字区间的出现次数时,就是这些数字在 Tj 中出现的次数减去在 Ti-1 中出现的次数。
那么有修改操作怎么办呢?
如果将询问看成求一段序列的数字和,那么上面那个相当于求出了前缀和。加入修改操作后,就要用树状数组等来维护前缀和了。于是那个 “很好的性质” 又一次发挥了作用,由于主席树可以互相加减,所以可以用树状数组来套上它。做法和维护前缀和长得基本一样.
主席树中有一些性质方便我们进行运算
①主席树的每个结点,保存的是这个区间含有的数字的个数。
②主席树的每个结点,也就是每颗线段树的大小和形态也是一样的,也就是主席树之间可以相互进行加减运算。
……………………………………………………………………………………………………………………………………………………
然后还是看看这道模板题吧
题目背景
这是个非常经典的主席树入门题——静态区间第K小
题目描述
如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
输入格式:
第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。
第二行包含N个正整数,表示这个序列各项的数字。
接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间[l, r][l,r]内的第k小值。
输出格式:
输出包含k行,每行1个正整数,依次表示每一次查询的结果
输入样例:
5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
输出样例
6405
15770
26287
25957
26287
数据范围:
对于20%的数据满足:1≤N,M≤10
对于50%的数据满足:1≤N,M≤103
对于80%的数据满足:1≤N,M≤105
对于100%的数据满足: 1≤N,M≤2⋅105
对于数列中的所有数ai,均满足−109≤ai≤109
思路
这题其实思路就非常明确了
就是主席树
首先将值离散化之后,构建一颗值域线段树储存区间和
初始的线段树是空树
每次在值域上增加1就重构一颗线段树
很显然,任意两颗相邻线段树的值得和差为1
而相同的区间内要么相等要么多1
那么,我们也很容易的可以推出,区间第k大可以通过第r版本和第(l-1)版本的线段树算出来
每次计算左儿子
如果r的左儿子已经比(l-1)的左儿子多出来的和大于k
那么在左儿子查找k大值
否则则在右儿子上查找第(k-val)大值,其中,val是和的差
代码
#include <bits/stdc++.h>
using namespace std;
inline int read(){
int ret=0,f=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
for(;isdigit(c);c=getchar())ret=ret*10+c-'0';
return ret*f;
}
struct pppp{int l,r,sum,ls,rs;}tr[20000001];
int a[200005],b[200005],ls[200005];
int n,m,lsh[200005],pp=0,root[200005];
void build(int rt,int l,int r){
++pp;tr[rt].l=l;tr[rt].r=r;
if(l==r)return ;
int mid=(l+r)>>1;
tr[rt].ls=pp+1;
build(pp+1,l,mid);
tr[rt].rs=pp+1;
build(pp+1,mid+1,r);
}
void cr(int rt,int k){
++pp;int mid=(tr[rt].l+tr[rt].r)>>1;
tr[pp]=tr[rt];++tr[pp].sum;
if(tr[rt].l==tr[rt].r)return ;
if(k<=mid){tr[pp].ls=pp+1;cr(tr[rt].ls,k);}
else{tr[pp].rs=pp+1;cr(tr[rt].rs,k);}
}
int ch(int x,int y,int k){
if(tr[x].l==tr[x].r)return tr[x].l;
int tmp=tr[tr[y].ls].sum-tr[tr[x].ls].sum;
if(tmp>=k)return ch(tr[x].ls,tr[y].ls,k);
else return ch(tr[x].rs,tr[y].rs,k-tmp);
}
int main(){int x,y,z;
n=read();m=read();
for(int i=1;i<=n;++i)b[i]=read(),a[i]=b[i];
sort(b+1,b+n+1);
int nn=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=nn;++i)lsh[i]=b[i],ls[b[i]]=i;
build(1,1,nn);
root[0]=1;
for(int i=1;i<=n;++i){
root[i]=pp+1;
cr(root[i-1],ls[a[i]]);
}
while(m--){
x=read();y=read();z=read();
printf("%d\n",lsh[ch(root[x-1],root[y],z)]);
}
return 0;
}