可持久化线段树(prizident tree)学习笔记
一、定义:“可持久化”定义:可以支持回退,访问之前版本的数据结构;
主席树:可以访问未经过其他操作的版本的线段树
二、原理:
主席树与线段树的基本操作相同,唯一的难点在于如何实现可持久化。
如果想要访问每个版本的线段树,首先想到的是对线段树进行全盘复制,然后在上个版本的基础上进行本次操作以建立新版本。但很显然如此一来空间复杂度会直接窜到O(n*m),显然不可行
考虑操作对线段树的作用:每次操作一定不会涉及线段树上所有的节点,而不涉及的节点必然在两个版本中是相同的,也就是可以共享的
以此为切入点,如果我们在每次进行操作时只修改被影响的节点,可以得到如下的结果(洛咕借的图)
具体的做法是,进行每次操作时复制上个版本的根作为新版本的根,再递归判断修改的子节点进行修改
代码:
inline int update(int pre, int l, int r, int x)
{
int rt=++cnt;
L[rt]=L[pre]; R[rt]=R[pre]; sum[rt]=sum[pre]+1;
if (l<r)
{
if (x <= mid) L[rt] = update(L[pre], l, mid, x);
else R[rt]=update(R[pre], mid+1, r, x);
}
return rt;
}
查询时直接调用版本对应的根就可以咯
洛咕板子:查询静态区间第k小
与主席树的联系:把序列从左到右插入到树中,没插入一次对应一个版本,在l——r间查询对应在版本l和r间进行比较查询
建树方法:先把整个序列进行离散化,线段树维护每个数出现过多少次
比较方法:导入两个根,先对两者左儿子中数的个数进行比较,若比k小则说明k在右儿子中,进行递归
完整代码:
#include<cstdio>
#include<algorithm>
#define maxn 200010
#define mid (l+r)/2
using namespace std;
int ls[maxn<<5],rs[maxn<<5],sum[maxn<<5];
int a[maxn],b[maxn],cnt=0,t[maxn<<5];
int n,m,q;
int build(int l,int r)
{
int rt=++cnt;
sum[rt]=0;
if(l<r)
{
ls[rt]=build(l,mid);
rs[rt]=build(mid+1,r);
}
return rt;
}
int update(int pre,int l,int r,int k)
{
int rt=++cnt;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
sum[rt]=sum[pre]+1;
if(l<r)
{
if(k<=mid)
ls[rt]=update(ls[pre],l,mid,k);
else
rs[rt]=update(rs[pre],mid+1,r,k);
}
return rt;
}
int query(int u,int v,int l,int r,int k)
{
if(l>=r)
return l;
int usm=sum[ls[v]]-sum[ls[u]];
if(k<=usm)
return query(ls[u],ls[v],l,mid,k);
else
return query(rs[u],rs[v],mid+1,r,k-usm);
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);
m=unique(b+1,b+n+1)-b-1;
t[0]=build(1,m);
for(int i=1;i<=n;i++)
{
int p=lower_bound(b+1,b+m+1,a[i])-b;
t[i]=update(t[i-1],1,m,p);
}
while(q--)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
printf("%d\n",b[query(t[x-1],t[y],1,m,z)]);
}
return 0;
}