学习完平衡树,开始学习主席树。
##何为主席树
一种树,一种离线的数据结构,一种线段树,全名叫函数式版本的线段树。每一个节点存的是一段数字区间内出现的次数,但是它和线段树的区别是,他有一堆根。
为何得名主席树呢?发明者的简拼是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问题下架了补一下