主席树

今天下午学会了主席树,写写自己的理解。

  • 主席树,也叫可持久化权值线段树。
  • 什么叫权值线段树?就是线段树的叶子结点保存的是当前值的个数。
  • 什么叫可持久化?可持久化就是可以保留以前建树的历史版本,比如你要对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));
		}
	}
 } 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值