可持久化线段树入门浅谈
Posted lbssxz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了可持久化线段树入门浅谈相关的知识,希望对你有一定的参考价值。
很多人至今为止学到或者遇到的题目都是“维护数据结构的最新状态”之类的题目,当我们遇上“询问某一历史版本中balabala”,甚至要修改某一历史版本某一数值的题目时,我们便考虑可持久化数据结构。
可持久化数据结构是在基础的数据结构上记录每一次操作的历史版本来做到随时访问的目的。
在这一篇博客,我们要学习可持久化线段树。
由于题目文字符号格式不一致,这里不再复制题目描述,可自行点入链接或分屏观看说白了就是我很懒qwq
朴素方法:
考每进行一次操作,我们copy整棵线段树,对根节点进行编号来记录版本,访问时访问对应的线段树即可。
差不多是这样:
左边为初始版本,右边为二次版本,标黄的地方是经过修改的点和边,也就是说我们对a[4]进行了修改,然后往上更新了两层,影响到了5和1节点。
严重的问题:你空间炸了
正解:
我们可以发现这一部分:
也就是橙色部分,完全一样,可以优化内存。
也就是我们考虑重复利用,如果要从新版本(右边黄色)部分要访问左边未曾修改过的节点,则我们可以向左连边,变成这个亚子:
这样我们可以直接从8向左访问到2,也可以直接从9向左访问到6,并且省去了重复部分的空间。
至于节点编号,我们只需从1开始一个一个新建即可,不必像普通线段树那样。
思想就是这样,接下来是代码实现:
建立初始版本的函数maketree:
int maketree(int l,int r)//左右边界 { int now=++top;//top为总节点个数,新建一个节点,意味着top+1 if(l==r)//如果访问到最底层 { tree[now].val=a[l];//对应树的节点赋值 return now;//看下方代码,下一层往上传递的是节点编号(ls,rs为左儿子,右儿子,是节点编号),now就是当前节点编号 } int mid=(l+r)>>1;//好理解 tree[now].ls=maketree(l,mid); tree[now].rs=maketree(mid+1,r); return now;//传递的还是编号 }
个人感觉比线段树的建树还要简单。
修改函数update:
int update(int now,int l,int r,int x,int val) { //now是当前节点编号,l,r是左右界,x是要修改的节点编号,也就是a[x],val是要修改的值 int p=++top;新建节点啦~ tree[p]=tree[now];//感性李姐,或者去下方 if(l==r)//到那个位置了 { tree[p].val=val;//改 return p;//上一层需要下一层新节点的编号 } int mid=(l+r)>>1; if(x<=mid) { tree[p].ls=update(tree[now].ls,l,mid,x,val); } else { tree[p].rs=update(tree[now].rs,mid+1,r,x,val); } return p;//编号 }
关于tree[p]=tree[now]这个点,我做个说明:
新节点首先需要copy原先节点的信息,之后再修改其中一条边的信息。
首先我们新建一个节点,此时新节点编号为p,除此之外左右儿子与原节点相同。
接着,从底层更新上来后,新节点对应的其中一条边被修改了,就成了上上图:
修改一次复杂度为log[n]
接下来是查询函数query:
int query(int now,int l,int r,int x) { if(l==r) { return tree[now].val; } int mid=(l+r)>>1; if(x<=mid) return query(tree[now].ls,l,mid,x); else return query(tree[now].rs,mid+1,r,x); }
感觉这是最简单的函数。
AC代码:
#include<cstdio> #include<iostream> #include<cstring> #define N 1000007 using namespace std; inline int read() { int ans=0; char ch=getchar(),last=‘ ‘; while(ch<‘0‘||ch>‘9‘)last=ch,ch=getchar(); while(ch>=‘0‘&&ch<=‘9‘)ans=(ans<<3)+(ans<<1)+ch-‘0‘,ch=getchar(); return last==‘-‘?-ans:ans; } struct node { int ls,rs,val; }tree[N*20]; int top,a[N],n,m,judge,root[N],rt,v,upd; int maketree(int l,int r) { int now=++top; if(l==r) { tree[now].val=a[l]; return now; } int mid=(l+r)>>1; tree[now].ls=maketree(l,mid); tree[now].rs=maketree(mid+1,r); return now; } int update(int now,int l,int r,int x,int val) { int p=++top; tree[p]=tree[now]; if(l==r) { tree[p].val=val; return p; } int mid=(l+r)>>1; if(x<=mid) { tree[p].ls=update(tree[now].ls,l,mid,x,val); } else { tree[p].rs=update(tree[now].rs,mid+1,r,x,val); } return p; } int query(int now,int l,int r,int x) { if(l==r) { return tree[now].val; } int mid=(l+r)>>1; if(x<=mid) return query(tree[now].ls,l,mid,x); else return query(tree[now].rs,mid+1,r,x); } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) a[i]=read(); root[0]=maketree(1,n);//root0对应初始版本 for(int i=1;i<=m;i++) { rt=read(),judge=read(),v=read(); if(judge==1) { upd=read(); root[i]=update(root[rt],1,n,v,upd);//第i个版本 } else { printf("%d ",query(root[rt],1,n,v)); root[i]=root[rt];//直接copy树根即可 } } return 0; }
蒟蒻浅谈,不吝赐教。
完结撒花~
以上是关于可持久化线段树入门浅谈的主要内容,如果未能解决你的问题,请参考以下文章