静态主席树
在谈什么是主席树之前,不得不提这个名字的来历……发明主席树的dalao首字母缩写为(HJT)正好是某位伟人的名字,因此得名主席树( ̄▽ ̄)/
咳咳,现在我们正式介绍什么是主席树。
主席树实际上是一种有点抽象的数据结构,它所维护的每一个节点都是一颗线段树,因此我们需要一个(root)数组用以记录每个根节点的编号。而它的线段树则是维护区间([1,i]),([1,i+1]),……的前缀,是一种可持久化数据结构,你可以查询它历史版本的信息,或者通过其可加,可减的性质完成对某区间的查找。
例如想要查询第[l,r]区间的信息,那么,我们只需对(root[r]-root[l-1])求前缀和即可。
显然,直接这样暴力对每个节点建树会MLE成傻子,但是我们动动脑筋就会发现:每次我们更新的仅仅是我们需要修改的点到根这条链上的信息,因此我们只需要重构这一条链,与上一个线段树相同的节点共用一下就行了。
如图:
这是我们的root[0],是一颗空树,接着我们加入一号元素(节点上的编号是其所属的线段树)
就这样,我们就可以完成空间上的节省
code:
#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;
struct node {
int l,r,sum;
node() {
sum=0;//记录维护区间中的多少个数
}
} t[100005*40];
int n,m,tot;
int root[100005],a[100005];
vector<int>v;
int getid(int x)
{
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void modify(int l,int r,int &x,int y,int pos)
{
t[x=++tot]=t[y],t[x].sum++;
if(l==r) return;
int mid=l+r>>1;
if(pos<=mid) modify(l,mid,t[x].l,t[y].l,pos);//小于mid找左子树
else modify(mid+1,r,t[x].r,t[y].r,pos);//大于mid找右子树
}
int query(int l,int r,int x,int y,int k)
{
if(l==r) return l;
int mid=l+r>>1;
int del=t[t[y].l].sum-t[t[x].l].sum;
if(k<=del) return query(l,mid,t[x].l,t[y].l,k);
else return query(mid+1,r,t[x].r,t[y].r,k-del);//删除左子树贡献
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) scanf("%d",&a[i]),v.push_back(a[i]);
sort(v.begin(),v.end()),v.erase(unique(v.begin(),v.end()),v.end());//离散化
for(int i=1; i<=n; i++) modify(1,n,root[i],root[i-1],getid(a[i]));
while(m--) {
int x,y,k;
scanf("%d%d%d",&x,&y,&k);
printf("%d
",v[query(1,n,root[x-1],root[y],k)-1]);//还原离散化的值
}
}
动态主席树
待填坑