学习博客:http://www.cnblogs.com/flashhu/p/8297581.html
题目链接:https://www.luogu.org/problemnew/show/P3919
很裸的可持久化线段树板子题。可持久嘛!就是当出现历史版本的时候,能够非常方便地维护一个区间的历史版本。
自然,我们需要建NN棵线段树。最粗暴的想法,对每个新版本都把原版本内容复制一遍,然后修改对应的值。这根本不用想,直接MLE+TLE。那维护历史版本又是怎样实现的呢?
对于本题,每个版本的序列,我们可以建一棵线段树来维护它,所有非叶子节点表示的是一段区间,而叶子节点就表示序列的每一个值了。
举个栗子,样例中初始版本可以长这样——
而版本1只是查询了一下(线段树基本操作,这里不再赘述),然后跟初始版本一模一样。这就没必要复制了嘛!我们设版本ii有一个根节点rootirooti(表示整段区间),根节点有左右儿子,那么我们直接让root1root1的左右儿子指向root0root0的左右儿子就好了,根本不用复制整个线段树嘛!
那再来看看修改操作。比如从版本1~2。1和0是一样的,而版本2会长这样——
有没有发现1和2真的很像?其实从前到后只改变了一个节点!那么其他相同的地方,我们可不可以共用一段内存呢?
没错,每次创建一个新的版本时,只要新建log2nlog2n个节点,也就是只保存从新版本的根节点到更新的那一个叶子节点的路径就可以了,不在此路径上的左/右儿子只要接原版本对应区间的对应儿子就可以啦。我们可以保证,从对应版本的根节点一定能访问到对应叶子节点的值。
看代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long LL; const LL mod=1e9+7; const LL INF=1e9+7; const int maxn=1e6+50; int N,M; int tot=0; int a[maxn];//输入数组 int root[maxn<<5];//root[i]表示版本号问i的线段树的根节点编号 数组开大点 struct Node { int lc,rc,v; }t[maxn<<5];//数组开大点 int Build(int l,int r) { int pos=++tot; if(l==r) { t[pos].v=a[l]; return pos; } int mid=(l+r)>>1; t[pos].lc=Build(l,mid); t[pos].rc=Build(mid+1,r); return pos; } int Update(int old,int tar,int c,int l,int r)//老版本 修改a[tar]为c { int pos=++tot; if(l==r) { t[pos].v=c; return pos; } t[pos].lc=t[old].lc; t[pos].rc=t[old].rc;//复制老树 int mid=(l+r)>>1; if(tar<=mid) t[pos].lc=Update(t[old].lc,tar,c,l,mid); else t[pos].rc=Update(t[old].rc,tar,c,mid+1,r); return pos; } int query(int pos,int p,int l,int r) { if(l==r) { return t[pos].v; } int mid=(l+r)>>1; if(p<=mid) return query(t[pos].lc,p,l,mid); else return query(t[pos].rc,p,mid+1,r); } int main() { scanf("%d%d",&N,&M); for(int i=1;i<=N;i++) scanf("%d",&a[i]); root[0]=Build(1,N); int v,x,l,w; for(int i=1;i<=M;i++) { scanf("%d%d%d",&v,&x,&l); if(x==1) { scanf("%d",&w); root[i]=Update(root[v],l,w,1,N); } else { root[i]=root[v];//直接复制就行 因为都一样 printf("%d ",query(root[v],l,1,N)); } } return 0; }