Persistent segment tree

Keywords: data structure

Before talking about the persistent segment tree, you need to know what the segment tree is. If you still don't know the segment tree, you can take a look at the template I wrote about the segment tree and attach the blog address here: (63 messages) segment tree template_ AC__dream blog - CSDN blog

We know that the segment tree supports modification and query operations. If we want to operate on a historical version of the segment tree, how should we achieve our goal? The simplest and crudest method is to copy the original segment tree for each modification, and then operate on the newly established segment tree, so that we can operate on any previous version of the segment tree, but this is obviously not desirable, whether in terms of time complexity or space complexity, The cost of doing so is too high. For example, if we want to modify the first node, which nodes in the segment tree will change? Whether all nodes on the connection from leaf node 1 to root node will change, and other nodes other than these nodes will not change, we should now think about whether we can create only one branch chain and reuse other branches on the previous segment tree to form a new segment tree. We can only operate on the newly generated chain, This will greatly increase the utilization of space.

The persistent segment tree will add new branches with the operation, so we can't get the child node number by multiplying the parent node by two. This is also the biggest difference between a persistent segment tree and an ordinary segment tree.

Let me talk about the detailed implementation steps of the template.

Note that the following template does not involve interval update. If interval update is designed, lazy tag is required.

Achievements:

void build(int id,int L,int R)
{
	l[id]=L;r[id]=R;sum[id]=0;
	if(L==R)
	{
		sum[id]=a[L];
		return ;
	}
	int mid=L+R>>1;
	//The left and right child nodes of the persistent segment tree cannot be obtained directly by multiplying 2, and new nodes are required every time 
	ln[id]=++idx;rn[id]=++idx;
	build(ln[id],L,mid);
	build(rn[id],mid+1,R);
	pushup(id);
}

Single point update:

//pre represents the segment tree of the old version, and id represents the segment tree of the current version
void update_point(int pre,int id,int pos,int val)
{
	//Assignment of new version by old version 
	rn[id]=rn[pre];ln[id]=ln[pre];l[id]=l[pre];r[id]=r[pre],sum[id]=sum[pre];
	if(l[id]==r[id])
	{
		sum[id]=val;
		return ;
	}
	int mid=l[id]+r[id]>>1;
    //If the point to be updated is on the left child, we can reuse the right child and create a new left child
	if(pos<=mid) ln[id]=++idx,update_point(ln[pre],ln[id],pos,val);
    //If the point to be updated is on the right child, we can reuse the left child and create a new right child
	else rn[id]=++idx,update_point(rn[pre],rn[id],pos,val);
	pushup(id);
}

Single point inquiry:

//Single point query is the same as ordinary segment tree. We only need to pass in the segment tree version we currently want to query 
int query_point(int id,int x)
{
	if(l[id]==r[id]) return sum[id];
	int mid=l[id]+r[id]>>1;
	if(x<=mid) return query_point(ln[id],x);
	else return query_point(rn[id],x);
}

Interval query:

//To query the segment tree on l~r, we need to subtract the segment tree of R version and l-1 version 
int query_interval(int pre,int id,int L,int R)
{
    //The new and old version of the line segment tree can be subtracted to obtain the line segment tree operating on l~r
	if(l[id]>=L&&r[id]<=R) return sum[id]-sum[pre];
	int mid=l[id]+r[id]>>1;
	int ans=0;
	if(L<=mid) ans+=query_interval(ln[pre],ln[id],L,R);
	if(mid+1<=R) ans+=query_interval(rn[pre],rn[id],L,R);
	return ans;
}

Here are two template questions:

(single point modification + single point query) title link: P3919 [template] persistent segment tree 1 (persistent array) - Luogu | new ecology of Computer Science Education (luogu.com.cn)

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int M=1000010,N=3e7+10;
int l[N],r[N],ln[N],rn[N],a[M],root[M];
int sum[N],idx;
//The biggest difference between the persistent segment tree and the ordinary segment tree is that the ordinary segment tree can obtain the child node by multiplying the parent node by two 
void pushup(int id)
{
	sum[id]=sum[ln[id]]+sum[rn[id]];
}
void build(int id,int L,int R)
{
	l[id]=L;r[id]=R;sum[id]=0;
	if(L==R)
	{
		sum[id]=a[L];
		return ;
	}
	int mid=L+R>>1;
	//The left and right child nodes of the persistent segment tree cannot be obtained directly by multiplying 2, and new nodes are required every time 
	ln[id]=++idx;rn[id]=++idx;
	build(ln[id],L,mid);
	build(rn[id],mid+1,R);
	pushup(id);
}
//pre represents the segment tree of the old version, and id represents the segment tree of the current version
void update_point(int pre,int id,int pos,int val)
{
	//Assignment of new version by old version 
	rn[id]=rn[pre];ln[id]=ln[pre];l[id]=l[pre];r[id]=r[pre],sum[id]=sum[pre];
	if(l[id]==r[id])
	{
		sum[id]=val;
		return ;
	}
	int mid=l[id]+r[id]>>1;
	if(pos<=mid) ln[id]=++idx,update_point(ln[pre],ln[id],pos,val);
	else rn[id]=++idx,update_point(rn[pre],rn[id],pos,val);
	pushup(id);
}
//Single point query is the same as ordinary segment tree. We only need to pass in the segment tree version we currently want to query 
int query_point(int id,int x)
{
	if(l[id]==r[id]) return sum[id];
	int mid=l[id]+r[id]>>1;
	if(x<=mid) return query_point(ln[id],x);
	else return query_point(rn[id],x);
}
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	root[0]=++idx;
	build(root[0],1,n);
	int t,op,loc,val;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&t,&op);
		if(op==1)
		{
			scanf("%d%d",&loc,&val);
			root[i]=++idx;
			update_point(root[t],root[i],loc,val);
		}
		else
		{
			scanf("%d",&loc);
			printf("%d\n",query_point(root[t],loc));
			root[i]=root[t];
		}
	}
	return 0;
}

(monotonic update + interval query) title link: P3834 [template] persistent segment tree 2 - New Ecology of computer science education in Luogu (luogu.com.cn)

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
const int N=2e7+10;
int a[N],l[N],r[N],sum[N],ln[N],rn[N],idx,root[N];
vector<int> alls;
void pushup(int id)
{
	sum[id]=sum[ln[id]]+sum[rn[id]];
} 
void build(int id,int L,int R)
{
	l[id]=L;r[id]=R;sum[id]=0;
	if(l[id]==r[id]) return ;
	int mid=L+R>>1;
	ln[id]=++idx;rn[id]=++idx;
	build(ln[id],L,mid);
	build(rn[id],mid+1,R);
	pushup(id);
}
void update_point(int pre,int id,int pos,int val)
{
	l[id]=l[pre];r[id]=r[pre];ln[id]=ln[pre];rn[id]=rn[pre];sum[id]=sum[pre];
	if(l[id]==r[id])
	{
		sum[id]+=val;
		return ;
	}
	int mid=l[id]+r[id]>>1;
	if(pos<=mid) ln[id]=++idx,update_point(ln[pre],ln[id],pos,val);
	else rn[id]=++idx,update_point(rn[pre],rn[id],pos,val);
	pushup(id);
	return ;
}
//To query the segment tree on l~r, we need to subtract the segment tree of R version and l-1 version 
int query_interval(int pre,int id,int L,int R)
{
	if(l[id]>=L&&r[id]<=R) return sum[id]-sum[pre];
	int mid=l[id]+r[id]>>1;
	int ans=0;
	if(L<=mid) ans+=query_interval(ln[pre],ln[id],L,R);
	if(mid+1<=R) ans+=query_interval(rn[pre],rn[id],L,R);
	return ans;
}
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		alls.push_back(a[i]);
	}
	sort(alls.begin(),alls.end());
	alls.erase(unique(alls.begin(),alls.end()),alls.end());
	for(int i=1;i<=n;i++) a[i]=lower_bound(alls.begin(),alls.end(),a[i])-alls.begin()+1;
	root[0]=++idx;
	build(root[0],1,alls.size());
	for(int i=1;i<=n;i++)
	{
		root[i]=++idx;
		update_point(root[i-1],root[i],a[i],1);
	}
	int lans,rans,k;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&lans,&rans,&k);
		int ll=0,rr=alls.size();
		while(ll<rr)
		{
			int mid=ll+rr+1>>1;
			if(query_interval(root[lans-1],root[rans],1,mid)<k) ll=mid;
			else rr=mid-1;
		}
		printf("%d\n",alls[ll]);
	}
	return 0;
}

The operation of the persistent segment tree in this blog does not involve interval updating. In the future blog, I will write a separate blog to introduce the interval modification in the persistent segment tree.

Posted by jpraj on Tue, 09 Nov 2021 00:19:11 -0800