[模板] 主席树
define 想不清楚回到定义
可持久化权值线段树
个人理解:
-
与线段树不同的地方在于,线段树维护的是某一段序列的某个特征。
-
带权线段树维护的是区间的桶,以数据的值作为下标,像桶一样记录数据出现的次数,且带有线段树的特征。
主席树
-
主席树是一种特殊的带权二叉树,或者说他由 n 个带权二叉树组成,可以在很快的时间内求出任意子序列的第 k 小值。
-
主席树采用动态开点存储。
原理
假如我有 \(1-n\) 这 \(n\) 个数,每有一个数,我就形成一棵带权二叉树(可以先这么理解),那么要查询区间 \([ l , r ]\) 上的第 k 小值,就让第 r 棵带权二叉树与第 l-1 棵相减,设某个对应结点为[ x ,y ],那么差值就表示 [ l , r ]上有这么多个数在[ x , y ]之间。是不是很简单?
其他操作类似于线段树,不在赘述。
Updated on 2021-06-14
今天重新捡起了主席树,发现当时码风极其独特 markdown 也极其独特
重新更新一下代码,包含了自己动态开点线段树的习惯,好记。
总代码
//Shiyan Wang
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
char ch=getchar();bool fl=false;T x=0;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
const int maxn = 2e5 + 10;
#define mid ((l+r)>>1)
int n,q,m,cnt=0;
int a[maxn],b[maxn],rt[maxn];
int sz[maxn<<5],ls[maxn<<5],rs[maxn<<5];
void build(int &p,int l,int r){
p=++cnt;
if(l==r)return ;
sz[p]=0;
build(ls[p],l,mid);build(rs[p],mid+1,r);
}
void update(int &p,int pre,int l,int r,int val){
p=++cnt;
ls[p]=ls[pre],rs[p]=rs[pre],sz[p]=sz[pre]+1;
if(l==r)return ;
if(val<=mid)update(ls[p],ls[pre],l,mid,val);
else update(rs[p],rs[pre],mid+1,r,val);
}
int query(int u,int v,int l,int r,int k){
if(l>=r)return l;
int x=sz[ls[v]]-sz[ls[u]];
if(x>=k)return query(ls[u],ls[v],l,mid,k);
else return query(rs[u],rs[v],mid+1,r,k-x);
}
#define read() read<int>()
int main(){
n=read();q=read();
for(int i=1;i<=n;i++)a[i]=read(),b[i]=a[i];
sort(b+1,b+1+n);
m=unique(b+1,b+1+n)-(b+1);
build(rt[0],1,m);//权值线段树
for(int i=1;i<=n;i++){
int pos=lower_bound(b+1,b+1+m,a[i])-b;
update(rt[i],rt[i-1],1,m,pos);
}
while(q--){
int x=read(),y=read(),z=read();
printf("%d\n",b[query(rt[x-1],rt[y],1,m,z)]);
}
return 0;
}