西安多校集训-主席树

前言

这不就是寒假集训时 dbg 学长讲的线段树上二分吗?感觉好像。

思想

可持久化权值线段树简称主席树。
先说可持久化:

可持久化

我们发现如果只有单点修改,一颗线段树最多被修改 \(\log n\) 个结点(也就是一条链),所以直接新建这 \(\log n\) 个结点,将其指向根节点的同时,指向对应的过去结点。这样就可以做到时空均为 \(O(n\log n)\) 的可接受复杂度。当然需要动态开点。

主席树

可以用于静态查询区间第 k 小或第 k 大。
建立一棵权值线段树,树上维护每个数的数量。每次加入一个结点就新开一个版本的线段树,然后进行树上二分,运用前缀和思想。

#include<iostream>
#include<algorithm>
using namespace std;
const int N=2e5+50;
int ls[N<<5],rs[N<<5],sum[N<<5];
int ver[N<<5],tot;
int n,m;
int a[N],b[N],len;
int build(int l,int r){
	int now=++tot;
	if(l==r) return now;
	int mid=(l+r)>>1;
	ls[now]=build(l,mid);
	rs[now]=build(mid+1,r);
	return now;
}
int insert(int l,int r,int pre,int k){
	int now=++tot;
	ls[now]=ls[pre];rs[now]=rs[pre];sum[now]=sum[pre]+1;
	if(l==r) return now;
	int mid=(l+r)>>1;
	if(k<=mid){
		ls[now]=insert(l,mid,ls[pre],k);
	}else{
		rs[now]=insert(mid+1,r,rs[pre],k);
	}
	return now;
}
int query(int now,int pre,int l,int r,int k){
	if(l==r) return l;
	int mid=(l+r)>>1;
	int x=sum[ls[now]]-sum[ls[pre]];
	if(k<=x){
		return query(ls[now],ls[pre],l,mid,k);
	}else{
		return query(rs[now],rs[pre],mid+1,r,k-x);
	}
}
int find(int x){
	return lower_bound(b+1,b+1+len,x)-b;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		b[i]=a[i];
	}
	sort(b+1,b+1+n);
	len=unique(b+1,b+1+n)-b-1;
	ver[0]=build(1,len);
	for(int i=1;i<=n;i++){
		ver[i]=insert(1,len,ver[i-1],find(a[i]));
	}
	for(int i=1;i<=m;i++){
		int l,r,k;
		cin>>l>>r>>k;
		cout<<b[query(ver[r],ver[l-1],1,len,k)]<<'\n';
	}
	return 0;
}

哦原来是用到线段树上二分了啊,那没事了。

posted @ 2025-03-31 20:20  Tighnari  阅读(17)  评论(0)    收藏  举报