主席树小结

学习完平衡树,开始学习主席树。

##何为主席树
一种树,一种离线的数据结构,一种线段树,全名叫函数式版本的线段树。每一个节点存的是一段数字区间内出现的次数,但是它和线段树的区别是,他有一堆根。

为何得名主席树呢?发明者的简拼是HJT。

主席树有一个神奇的性质:满足区间减法。

##如何实现

先假设我们没有修改操作。对于每个前缀s[i],保存一个线段树t[i],组成主席树。
经过观察,我们发现每棵线段树的形态相同,所以满足区间减法。对于询问(l,r),我们只需要拿出来t[r]和t[l-1],每个节点减一下即可。

那如果有修改呢?还记得树状数组可以维护前缀和吗?树状数组同样可以维护主席树。

一次性建n棵线段树是不是很明显会MLE???我们发现相邻的两颗线段树其实最多只有logn个节点不同(后一棵树只会比前一个在某一个叶子多1所以是log级别),采用类似于可持久化的做法,先建出一棵线段树,每次在原树上建一条新路径即可。

##主席树的操作

###1.修改

主席树的修改类似于线段树,每次递归修改左右儿子。

int modify(int pre,int left,int right,int x)
{
	int now=++tot;
	if (left==right)
	{
		t[now].val=t[pre].val+1;
		t[now].lch=t[now].rch=0;
		return now;
	}
	int mid=(left+right)>>1;
	if (mid>=x)
	{
		t[now].rch=t[pre].rch;
		t[now].lch=modify(t[pre].lch,left,mid,x);
	}
	else
	{
		t[now].lch=t[pre].lch;
		t[now].rch=modify(t[pre].rch,mid+1,right,x);
	}
	t[now].val=t[t[now].lch].val+t[t[now].rch].val;
	return now;
}

###2.查询
查询时分情况讨论,像前缀和一样查出左右子树的数的个数,若左子树总数大于k,则向左子树继续询问,否则向右子树询问。

int query(int root1,int root2,int left,int right,int k)
{
	if (left==right)
		return left;
	int mid=(left+right)>>1;
	int num=t[t[root2].lch].val-t[t[root1].lch].val;
	if (num>=k)
		return query(t[root1].lch,t[root2].lch,left,mid,k);
	else
		return query(t[root1].rch,t[root2].rch,mid+1,right,k-num);
	return 0;
}

##主席树的应用

可以实现一些线段树不能实现的功能比如区间第k小。

#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define pb push_back

using namespace std;

inline ll read()
{
	long long f=1,sum=0;
	char c=getchar();
	while (c<'0' || c>'9')
	{
		if (c=='-')	f=-1;
		c=getchar();
	}
	while (c>='0' && c<='9')
	{
		sum=sum*10+c-'0';
		c=getchar();
	}
	return sum*f;
}
const int MAXN=200010;
const int MAXM=10000010;
map <int,int> mp;
struct re
{
	int pos,val,id;
};
re a[MAXN];
int cmpval(const re a,const re b)
{
	return a.val<b.val;
}
int cmpid(const re a,const re b)
{
	return a.id<b.id;
}
struct point
{
	int lch,rch,val;
};
point t[MAXM];
int root[MAXN],tot;
int modify(int pre,int left,int right,int x)
{
	int now=++tot;
	if (left==right)
	{
		t[now].val=t[pre].val+1;
		t[now].lch=t[now].rch=0;
		return now;
	}
	int mid=(left+right)>>1;
	if (mid>=x)
	{
		t[now].rch=t[pre].rch;
		t[now].lch=modify(t[pre].lch,left,mid,x);
	}
	else
	{
		t[now].lch=t[pre].lch;
		t[now].rch=modify(t[pre].rch,mid+1,right,x);
	}
	t[now].val=t[t[now].lch].val+t[t[now].rch].val;
	return now;
}
int query(int root1,int root2,int left,int right,int k)
{
	if (left==right)
		return left;
	int mid=(left+right)>>1;
	int num=t[t[root2].lch].val-t[t[root1].lch].val;
	if (num>=k)
		return query(t[root1].lch,t[root2].lch,left,mid,k);
	else
		return query(t[root1].rch,t[root2].rch,mid+1,right,k-num);
	return 0;
}
void init()
{
	tot=0,root[0]=0;
	t[0].lch=t[0].rch=t[0].val=0;
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i].val),a[i].id=i;
	sort(a+1,a+1+n,cmpval);
	for (int i=1;i<=n;i++)
		a[i].pos=i,mp[a[i].pos]=a[i].val;
	sort(a+1,a+1+n,cmpid);
	init();
	for (int i=1;i<=n;i++)
		root[i]=modify(root[i-1],1,n,a[i].pos);
	for (int i=1;i<=m;i++)
	{
		int l,r,k;
		scanf("%d%d%d",&l,&r,&k);
		int ans=query(root[l-1],root[r],1,n,k);
		printf("%d\n",mp[ans]);
	}
	return 0;
}

还加了离散化。

带修改的主席树以后附代码。

upd:因为zz问题下架了补一下

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值