可持久化线段树

可持久化线段树

可持久化线段树可以用于修改、维护序列任何一个历史副本

可持久化线段树1(可持久化数组)

只是个关于可持久化数据结构的引子,使用线段树来实现可持久化数组

我们熟知的线段树是长这样的(蓝色指的是修改节点的路径)

现在,我们既要修改也需要保存历史副本。如果我们强行再开一个数组,肯定会爆空间。事实上,我们如果将这个数组用线段树来维护,会发现只有修改路径上的节点需要新开空间,不收修改影响的节点是没必要新开,于是这张图就诞生了

总之就是给被修改的节点新开一个,且保留它和那些没有修改节点的关系

代码实现和线段树有点差别,具体看代码吧

struct chairman
{
	int ls[N],rs[N];
	int val[N*2],rt[N*2];
	//val[i]代表i号节点的值
	//rt[i]代表i号历史版本的根节点
	int cnt;//代表尾结点 
	int build(int l,int r) 
	{
		int root=++cnt;
		if(l==r)
		{
			val[l]=a[l];
			return root;
		}
		int mid=(l+r)/2;
		ls[root]=build(l,mid);
		rs[root]=build(mid+1,r);
		return root;
	}
	int update(int pre,int l,int r,int x,int c)
	{
		int root=++cnt;
		if(l==r)
		{
			val[root]=c;
			return root;
		}
		int mid=(l+r)/2;
		ls[root]=ls[pre];
		rs[root]=rs[pre];
		if(x<=mid) ls[root]=update(ls[pre],l,mid,x,c);
		else rs[root]=update(rs[pre],mid+1,r,x,c);
		return root;
	}
	int query(int pre,int l,int r,int x)
	{
		if(l==r)
		{
			return val[l];
		}
		int mid=(l+r)/2;
		if(x<=mid) return query(ls[pre],l,mid,x);
		else return query(rs[pre],mid+1,r,x);
	}
};

可持续化线段树2

主要思路是主席树+前缀和

不妨以1 5 2 6 3 7 4这组数据举个例子

首先,这题序列中数值范围过大,先对其进行离散化,将这些数转化成编号

接下来,我们在序列的每个位置建立一棵线段树(当然只是方便理解而已,如果真这么做会爆空间,解决方法下面讲)

而每个位置的线段树代表了什么呢?我们给位置\(i\)的线段树的每个节点赋一个有意义的区间 $ [a,b] $,代表了区间 \([1,i]\)中离散化后编号在\([a,b]\)中的数字的个数

然而听起来很抽象,还是来看例子

对于1位置上的线段树,显然只要把1放进去就行了

然后来到2号位置,需要先复制1号线段树,再插入5

3号位置,复制下2号线段树,再插入2

接下来以此类推啦,最后7位置上的线段树就是这样的了

这套操作给人一种用线段树做前缀和的感觉,显然,对于\((i-1)\)号线段树和\(j\)号线段树,通过对应节点相减,可以得到\(区间[i,j]中,离散化后编号在特定区间内的数有多少个\)

那么查询\([l,r]\)中的\(k\)小值时,我们假想一个把\(j\)号线段树和\(i-1\)对应位置相减得到的线段树,若左子树的权值\(x>=k\),就查询左子树的\(k\)小值,否则查询右子树的\(k-x\)小值

关于爆空间的烦恼?我们发现,这套操作和可持久化线段树1的操作是大同小异的,都是新建版本,部分修改,支持访问历史版本,所以我们也可以用1中的方法开新点,这样就解决了爆空间的问题了

代码(特别感谢Mercury_City大佬)

#include <bits/stdc++.h>
#define debug puts("I love Newhanser forever!!!!!");
#define pb push_back
using namespace std;
template <typename T>inline void read(T& t){
    t=0; register char ch=getchar(); register int fflag=1;
    while(!('0'<=ch&&ch<='9')){if(ch=='-') fflag=-1;ch=getchar();}
    while(('0'<=ch&&ch<='9')){t=t*10+ch-'0'; ch=getchar();} t*=fflag;
}
template <typename T,typename... Args> inline void read(T& t, Args&... args){read(t);read(args...);}
const int MAXN=10000086;
int n,m,root[MAXN],val[MAXN],a[MAXN],b[MAXN],tot,c[MAXN];
struct Segment_Tree{
	int lc[MAXN],rc[MAXN],cnt;
	#define Mid ((l+r)>>1)
	#define lson lc[u],l,Mid
	#define rson rc[u],Mid+1,r
	void build(int &u,int l,int r){
		u=++cnt;
		if(l==r)return;
		build(lson);build(rson);
	}
	void update(int &u,int l,int r,int pre,int pos,int v){
		u=++cnt; lc[u]=lc[pre]; rc[u]=rc[pre]; val[u]=val[pre]+v;
		if(l==r) return;
		if(pos<=Mid) update(lson,lc[pre],pos,v);
		else update(rson,rc[pre],pos,v);
	}
	int Query(int l,int r,int r1,int r2,int k){
		if(l==r) return l;
		int tmp=val[lc[r2]]-val[lc[r1]];
		if(tmp>=k) return Query(l,Mid,lc[r1],lc[r2],k);
		else return Query(Mid+1,r,rc[r1],rc[r2],k-tmp);
	}
}seg;
int main(){
	read(n,m);
	for(int i=1;i<=n;++i) read(a[i]),b[i]=a[i];
	sort(b+1,b+n+1);
	for(int i=1;i<=n;++i) if(i==1||b[i]!=b[i-1]) c[++tot]=b[i];
	seg.build(root[0],1,tot);
    for(int i=1;i<=n;++i) a[i]=lower_bound(c+1,c+tot+1,a[i])-c;
	for(int i=1;i<=n;++i) seg.update(root[i],1,tot,root[i-1],a[i],1);
	while(m--){
		int x,y,v;
		read(x,y,v);
		cout<<c[seg.Query(1,tot,root[x-1],root[y],v)]<<endl;
	}
    return 0;
}
posted @ 2022-06-12 19:45  羊扬羊  阅读(73)  评论(0编辑  收藏  举报