【模板】可持久化线段树(主席树)

(这是我的第一篇模板博客,希望不要出锅吧……)

主席树,也就是可持久化线段树,实际上就是查询区间 k 大的数据结构,并没有名字所说的那么牛逼,它只是一颗应用了可持久化思路的线段树而已。

那么什么是可持久化思路呢?

我们先看这个问题:查询区间 k 大。

最暴力的方法肯定是把每一段区间建一棵线段树,然后暴力去查。但是它的复杂度是 N 3 l o g N N^3 log N N3logN,GG。那有没有 N l o g N NlogN NlogN 的方法呢?我们考虑,如果对于区间内每一个位置,把它与区间最前端建一棵线段树,然后应用差分的思想查询,不是就可以 N l o g N NlogN NlogN 得到每一段区间的线段树了?

emmm。。。这个方法仿佛是可行的。那么如何对于每一个位置都只用 l o g N log N logN 的时间建一棵新线段树呢?

拿手画一下就会发现,对于每一个位置,都最多只会有 l o g N log N logN 的节点和前一棵线段树不一样。这意味着我们就可以愉快的用前面一棵线段树来建这一棵了。

建树具体思路很简单,对于每一个当前节点,只需判断下一个节点应该插在左边还是右边,然后新建这个节点,并将上一个版本线段树的对应子节点作为当前节点另一个子节点即可。

查询操作也差不了多少,对于每一个当前节点,只需判断当前版本线段树左子树的 sum 减去上一个版本线段树左子树的 sum 是否比 k 大即可,如果比 k 大,就去递归寻找当前节点左子树的第 k 大;如果比 k 小,就去递归寻找当前节点右子树的第 k-lsum 大即可。(lsum 就是上面减去后的结果)

剩下的就是代码实现了(附上代码以免忘记):

#include<iostream>
#define LIM 1000000000
using namespace std;
int n,m,cnt,root[20000005],ls[20000005],rs[20000005],sum[20000005];
void ins(int i,int pre,long long l,long long r,long long x)
{
	sum[i]=sum[pre]+1;
	if(l==r) return;
	long long mid=(l+r)/2;
	if(x<=mid)
	{
		if(!ls[i])	ls[i]=++cnt;
		rs[i]=rs[pre];
		ins(ls[i],ls[pre],l,mid,x);
	}
	else
	{
		if(!rs[i])	rs[i]=++cnt;
		ls[i]=ls[pre];
		ins(rs[i],rs[pre],mid+1,r,x);
	}
}
long long ques(int i,int pre,long long l,long long r,long long x)
{
	if(l==r) return l;
	long long mid=(l+r)/2,lsum=sum[ls[i]]-sum[ls[pre]];
	if(x<=lsum)	return ques(ls[i],ls[pre],l,mid,x);
	else	return ques(rs[i],rs[pre],mid+1,r,x-lsum);
}
int main()
{
	long long x,y,k;
	cin>>n>>m;cnt=n;
	for(int i=1;i<=n;i++)	root[i]=i;
	for(int i=1;i<=n;i++)	cin>>x,ins(root[i],root[i-1],1,LIM*2,x+LIM);
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y>>k;
		if(x>y)	swap(x,y);
		cout<<ques(root[y],root[x-1],1,LIM*2,k)-LIM<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值