用户工具

站点工具


2020-2021:teams:legal_string:jxm2001:可持久化数据结构_1

这是本文档旧的修订版!


可持续化线段树

算法简介

一种可以维护历史版本的线段树,时间复杂度和空间复杂度均为 $O\left(n+m\log n\right)$

算法思想

显然如果对每个历史版本都重建线段树保存时间复杂度和空间复杂度均为 $O\left(nm\right)$

但如果可以重复利用部分结点信息,则可以降低时间复杂度和空间复杂度

考虑仅对线段树修改路径上的点进行复制、修改,每次修改的时间复杂度和空间复杂度可降为$O\left(\log n\right)$

算法应用

可持续化数组

洛谷p3919

题意

维护一个长度为 $n$ 的数组,支持下列操作:

1. 在某个历史版本上修改某一个位置上的值

2. 访问某个历史版本上的某一位置的值

每进行一次操作就会生成一个新的版本,版本编号即为当前操作的编号

题解

用可持续化线段树维护数组,线段树叶子节点记录对应对应位置数组数值,时间复杂度和空间复杂度均为 $O\left(n+m\log n\right)$

查看代码

查看代码

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#define _for(i,a,b) for(int i=(a);i<(b);++i)
#define _rep(i,a,b) for(int i=(a);i<=(b);++i)
using namespace std;
typedef long long LL;
inline int read_int(){
	int t=0;bool sign=false;char c=getchar();
	while(!isdigit(c)){sign|=c=='-';c=getchar();}
	while(isdigit(c)){t=(t<<1)+(t<<3)+(c&15);c=getchar();}
	return sign?-t:t;
}
inline void write(LL x){
	register char c[21],len=0;
	if(!x)return putchar('0'),void();
	if(x<0)x=-x,putchar('-');
	while(x)c[++len]=x%10,x/=10;
	while(len)putchar(c[len--]+48);
}
inline void space(LL x){write(x),putchar(' ');}
inline void enter(LL x){write(x),putchar('\n');}
const int MAXN=1e6+5;
struct Node{
	int lef,rig,val;
};
Node node[MAXN*20];
int cnt,root[MAXN],a[MAXN];
int nodecopy(int k){
	node[++cnt]=node[k];
	return cnt;
}
void build(int &k,int lef,int rig){
	k=++cnt;
	int mid=lef+rig>>1;
	if(lef==rig)
	return node[k].val=a[mid],void();
	build(node[k].lef,lef,mid);
	build(node[k].rig,mid+1,rig);
}
void update(int &k,int p,int lef,int rig,int pos,int v){
	k=nodecopy(p);
	if(lef==rig)
	return node[k].val=v,void();
	int mid=lef+rig>>1;
	if(mid<pos)
	update(node[k].rig,node[p].rig,mid+1,rig,pos,v);
	else
	update(node[k].lef,node[p].lef,lef,mid,pos,v);
}
int query(int k,int lef,int rig,int pos){
	if(lef==rig)
	return node[k].val;
	int mid=lef+rig>>1;
	if(mid<pos)
	return query(node[k].rig,mid+1,rig,pos);
	else
	return query(node[k].lef,lef,mid,pos);
}
int main()
{
	int n=read_int(),q=read_int();
	_rep(i,1,n)
	a[i]=read_int();
	build(root[0],1,n);
	int rt,pos,v,opt;
	_rep(i,1,q){
		rt=read_int(),opt=read_int(),pos=read_int();
		if(opt==1){
			v=read_int();
			update(root[i],root[rt],1,n,pos,v);
		}
		else{
			root[i]=root[rt];
			enter(query(root[rt],1,n,pos));
		}
	}
	return 0;
}

静态区间第 $k$ 小

洛谷p3834

题意

维护一个长度为 $n$ 的数组,$m$ 次查询,每次查询给定闭区间内第 $k$ 小的数

题解

关于第 $k$ 小问题,首先想到使用类似名次树的方法查询

记数组元素为 $a[1\sim n]$,建立空线段树,依次在线段树第 $a[i]$ 位置插入一个元素,线段树需维护区间所含元素个数,并记录历史版本

设查询区间为 $[l,r]$ ,这历史版本 $r$ 与 历史版本 $l-1$ 的对应节点差值即为 元素 $a[l\sim r]$ 在节点维护范围内所含元素个数

在此基础上,便可以使用名次树查询第 $k$ 小元素,同时考虑到数据范围需要对数组进行离散化,时间复杂度和空间复杂度均为 $O\left(n+m\log n\right)$

查看代码

查看代码

 

可持久化并查集

洛谷p3402

题意

维护一个长度为 $n$ 的数组,支持下列操作:

1. 在某个历史版本上修改某一个位置上的值

2. 访问某个历史版本上的某一位置的值

每进行一次操作就会生成一个新的版本,版本编号即为当前操作的编号

题解

关于第 $k$ 小问题,首先想到使用类似名次树的方法查询

记数组元素为 $a[1\sim n]$,建立空线段树,依次在线段树第 $a[i]$ 位置插入一个元素,线段树需维护区间所含元素个数,并记录历史版本

设查询区间为 $[l,r]$ ,这历史版本 $r$ 与 历史版本 $l-1$ 的对应节点差值即为 元素 $a[l\sim r]$ 在节点维护范围内所含元素个数

在此基础上,便可以使用名次树查询第 $k$ 小元素,时间复杂度和空间复杂度均为 $O\left(n+m\log n\right)$

查看代码

查看代码

 
2020-2021/teams/legal_string/jxm2001/可持久化数据结构_1.1591006359.txt.gz · 最后更改: 2020/06/01 18:12 由 jxm2001