可持久化

可持久化数据结构简介

可持久化数据结构 (Persistent data structure) 总是可以保留每一个历史版本,并且支持操作的不可变特性 (immutable)。

可持久化数据结构分类

部分可持久化 (Partially Persistent)
所有版本都可以访问,但是只有最新版本可以修改。

完全可持久化 (Fully Persistent)
所有版本都既可以访问又可以修改。

若支持将两个历史版本合并,则又称为 Confluently Persistent。

实际应用

几何计算

在几何计算中有许多离线算法,如扫描线算法一次扫过去回答所有询问,在时间复杂度分析上相当优异。但强迫在线的情况下,每一次都扫描一次,询问操作的时间复杂度就从对数时间降成线性。为了解决这一种情况,持久化技术给了另一种思维,我们将扫描线的时间轴作为一个变动依据,持久化相关的结构,只要我们能将询问在对数时间内穿梭于这个时间轴,必能动态解决先前的问题。

字串处理

为了达到非常高效率的合并操作,防止大量重复性字串的生成伴随的效能退化,使得各方面的操作都能远低于线性操作。如 C++ rope 就是一个持久化的数据结构。不只是字串操作,若处理类型有大量重复的情况,持久化的概念便能派上用场。

版本回溯

实际上就是对应大部分的应用软体中的 redo/undo。如果资料库/操作变动为了高效率操作而会配上复杂的结构(并不像 hash, set 反转操作只需要常数或对数时间),那么为了快速回推变动结果,持久化结构就是要减少 redo/undo 的花费。

资料库本身可以常数回推,纪录变动的部分情况即可。而应用层的计算,大部分实作都是砍掉快取,并且重新计算出一份新的结构,有时候回推的变动大小为 m,为了重新计算结构而消耗了 n+m,如果 n 和 m 的差距非常大,那连续回推的体感就很糟糕。

函数式编程

函数式编程需要特别的数据结构以符合语言特性,其中不可变的性质更为重要,以利于并行环境与除错。如面向对象编程的 Java 8 后引入 stream 类,支援写出函数式的语法设计,可提供惰性求值、无限值域等的特殊功能。


主席树

新的版本在上一个版本的基础上建成。

一般用动态开点。

建树是递归建树。

插入时一边更改。

查询时查对应版本的树。

对数据进行离散化。

可持久化线段树1

点击查看代码
/*
主席树 
*/
const int N=1e6+10;
int n, m, v, op, rt, x, tot, tt, a[N], root[N];
struct node{
	int dat,l,r;
}t[N<<5];

il int read(){
	int f = 1, x = 0; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')f = -f;ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = x*10 + ch-'0';ch = getchar();}
	return f * x;
}

void pushup(int k){t[k].dat=t[t[k].l].dat+t[t[k].r].dat;}

void build(int &k,int l,int r){
	if(!k) {k=++tot;}
	if(l==r){t[k].dat=a[l];return ;}
	int mid=(l+r)>>1;
	build(t[k].l,l,mid);
	build(t[k].r,mid+1,r);
	pushup(k);
}

void update(int pre,int &k,int l,int r,int x,int v){
	k=++tot;
	t[k]=t[pre];
	if(l==r) {t[k].dat=v;return ;}
	int mid=(l+r)>>1;
	if(x<=mid)
		update(t[pre].l,t[k].l,l,mid,x,v);
	else update(t[pre].r,t[k].r,mid+1,r,x,v);
}

int query(int pre,int l,int r,int x){
	if(l==r) {return t[pre].dat;}
	int mid=(l+r)>>1;
	if(x<=mid)
		return query(t[pre].l,l,mid,x);
	return query(t[pre].r,mid+1,r,x);
}

signed main(){
	n = read(); m = read();
	rep(i,1,n) a[i]=read();
	build(root[0],1,n);
	rep(i,1,m) {
		rt=read();op=read();x=read();
		if(op==1){//在rt版本上将a[x]->v; 
			v=read();
			update(root[rt],root[++tt],1,n,x,v);//把历史版本弄到新的上面并把x上的值进行修改 
		}
		else {//在rt版本上查询a[x]的值并生成新的版本 
			root[++tt]=root[rt];
			cout<<query(root[rt],1,n,x)<<'\n';
		}
	}
	return 0;
}

可持久化线段树2

点击查看代码
const int N=2e5+10;
int n,m,x,l,r,tot,tt,root[N];
int c[N],rk[N],a[N];//离散化 
struct node{
	int dat,l,r,sum;
}t[N<<5];
/*
主席树
一个一个往里面插点 
*/
void pushup(int k){t[k].sum=t[t[k].l].sum+t[t[k].r].sum;}

void build(int &k,int l,int r){
	if(!k) k=++tot;
	if(l==r) {t[k].dat=c[l];return ;}
	int mid=(l+r)>>1;
	build(t[k].l,l,mid);
	build(t[k].r,mid+1,r);
	pushup(k);
}

void Insert(int pre,int &k,int l,int r,int x){
	k=++tot;
	t[k]=t[pre];
	if(l==r) {t[k].sum++;return ;};
	int mid=(l+r)>>1;
	if(x<=mid)
		Insert(t[pre].l,t[k].l,l,mid,x);
	else Insert(t[pre].r,t[k].r,mid+1,r,x);
	pushup(k);
}

int Query(int pre,int k,int l,int r,int x){
	if(l==r) return t[k].dat;
	int mid=(l+r)>>1,now=t[t[k].l].sum-t[t[pre].l].sum;
	if(x<=now)
		return Query(t[pre].l,t[k].l,l,mid,x);
	else return Query(t[pre].r,t[k].r,mid+1,r,x-now);
}

signed main(){
	n = read(); m = read();
	rep(i,1,n) a[i]=read(),c[i]=a[i];
//	memcpy(c,a,sizeof(c));//目标数组,数据源,大小
	sort(c+1,c+1+n);
	int len=unique(c+1,c+1+n)-(c+1);//起点,终点,从该处开始储存
	rep(i,1,n) {
		rk[i]=lower_bound(c+1,c+1+len,a[i])-c;
	}
	build(root[0],1,len);
	rep(i,1,n){
		Insert(root[i-1],root[i],1,len,rk[i]);
	}
	rep(i,1,m){
		l=read();r=read();x=read();
		cout<<Query(root[l-1],root[r],1,len,x)<<'\n';//查询第k小 
	}
	return 0;
}

神秘树

点击查看代码
const int N=2e5+10;
int n,m,l,r,ans,tot,a[N],root[N];
struct node{
	int dat,l,r;
}t[N<<5];
 
il int Max(int x, int y) {return x > y ? x: y;}
il int Min(int x, int y) {return x < y ? x: y;}

il int read(){
	int f = 1, x = 0; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')f = -f;ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = x*10 + ch-'0';ch = getchar();}
	return f * x;
}

void pushup(int k){t[k].dat=t[t[k].l].dat+t[t[k].r].dat;}

void Insert(int pre,int &k,int l,int r,int x){
	k=++tot;
	t[k]=t[pre];
	if(l==r) {
		t[k].dat+=x;
		return ; 
	}
	int mid=(l+r)>>1;
	if(x<=mid)
		Insert(t[pre].l,t[k].l,l,mid,x);
	else Insert(t[pre].r,t[k].r,mid+1,r,x);
	pushup(k);
}

int Query(int pre,int k,int l,int r,int fl,int fr){
	if(l>=fl&&r<=fr) return t[k].dat-t[pre].dat;
	int mid=(l+r)>>1,ans=0;
	if(fl<=mid) 
		ans+=Query(t[pre].l,t[k].l,l,mid,fl,fr);
	if(fr>mid) ans+=Query(t[pre].r,t[k].r,mid+1,r,fl,fr);
	return ans;
}

signed main(){
	n = read();
	rep(i,1,n) {
		a[i]=read();
		Insert(root[i-1],root[i],1,inf,a[i]);
	}
	m = read();
	rep(i,1,m){
		ans=1;
		l=read();r=read();
		while(1){
			int res=Query(root[l-1],root[r],1,inf,1,ans);//查询比ans小的数的和
			if(res>=ans) ans=res+1; 
			else break;
		}
		cout<<ans<<'\n';
	}
	return 0;
}

Rmq problem

点击查看代码
const int N=4e5+10;

int n,m,l,r,tot,tt,len,c[N],a[N],root[N];
struct node{
	int dat,l,r;
}t[N<<5];

il int read(){
	int f = 1, x = 0; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')f = -f;ch = getchar();}
	while(ch >= '0' && ch <= '9'){x = x*10 + ch-'0';ch = getchar();}
	return f * x;
}

void pushup(int k){t[k].dat=min(t[t[k].l].dat,t[t[k].r].dat);}

void Insert(int pre,int &k,int l,int r,int x,int val){
	k=++tot;
	t[k]=t[pre];
	if(l==r){t[k].dat=val;return ;}
	int mid=(l+r)>>1;
	if(x<=mid)
		Insert(t[pre].l,t[k].l,l,mid,x,val);
	else Insert(t[pre].r,t[k].r,mid+1,r,x,val);
	pushup(k);
}

int Query(int k,int l,int r,int x){
	if(!k||l==r) return c[l];
	int mid=(l+r)>>1;
	if(x<=t[t[k].l].dat)
		return Query(t[k].r,mid+1,r,x);
	return Query(t[k].l,l,mid,x);
}

signed main(){
	n=read();m=read();
	c[++tt]=0;
	rep(i,1,n){a[i]=read();c[++tt]=a[i];c[++tt]=a[i]+1;}
	sort(c+1,c+1+tt);
	len=unique(c+1,c+1+tt)-(c+1);
	rep(i,1,n){
		a[i]=lower_bound(c+1,c+1+len,a[i])-c;
		Insert(root[i-1],root[i],1,len,a[i],i);}
	rep(I,1,m){
		l=read();r=read();
		cout<<Query(root[r],1,len,l)<<'\n';
	}
	return 0;
}

2023.1.27 | 21:37

做题做到现在发现了一个规律:

练主席树:

做题 ×

贺题解 √

posted @ 2023-06-19 19:39  XYini  阅读(7)  评论(0)    收藏  举报