一、可持久化线段树的用处
显然从名字上可以看出来这是一种建立在线段树上的小升级。
从一个小问题可以引入:
当我们使用线段树处理一堆询问时,我们在一棵树上进行更新(如单点更新的操作),如果某一时刻突然让你把手头上的操作停下来,并且询问在你没有进行上一步操作时,树的具体数据。最容易想到的做法就是我退一步回去,把某一单点的值减掉,然后再把树拎出来给大家看。
那么如果再让你还原2步之前的树的样子,3步之前、4步之前……甚至k步之前树的模样又该怎么办呢。其实也很容易办到,就是在一开始操作的时候每一步我都复制一遍原来的树,然后再在新的树上做更新,那么无论询问多少步之前,都可以迅速找到需要的树。
问题就在于每一步我都复制了一遍之前的树,第一时间上不允许,第二在空间上需要的容量已经爆炸掉了。
可持久化线段树就是把这多棵线段树融为了一个,解决了时间与空间上的难题。
二、可持久化线段树是什么
总是习惯把一个数据结构画出来,这样才能理解。
首先一个正常的线段树长这样,
我们发现,我们在单点的进行修改时,只会顺着一条枝干向下进行更新,而另外一条枝上的数据我们是不会去修改的。比如我们要修改[ 6 , 10 ]范围上的某点,那么他的另外一侧[ 1 , 5 ]实际上是不会被用到的。回顾之前的问题,如果我们要建立多棵线段树,但是每一个新的树上却只有很少量的信息是和旧树不同的,那么如果我们不把这些旧的信息用起来岂不是很浪费?
所以大大们就想到了(%%%),利用旧树的信息来重建新的树。
比如现在我需要修改[ 6 , 10 ]范围上的某点,那么我们建立一个新的节点,并且把这个新节点的左儿子接在之前树上的 [ 1 , 5 ] 一侧,欸,我们发现这样的话不就已经完成了左边所有的子树的建立吗,因为1,5一侧不需要更新呀,所以我们不准备给他开新的节点了,直接接上旧的还能用。剩下的只需要操作右边就好啦。
保持这种接上旧的还能用的想法,做个几层(假设我们现在需要更新10这个点),我们发现只多出来了几个新节点,大致如下图:
仅用一点点的空间就建立了一棵新的树,也就是说,我们现在只需要知道每棵新的树的头节点就可以把新的树拎起来啦。
所以现在的问题就是该如何定义每个节点的编号,显然此时之前定义普通线段树节点的方式(左边*2,右边*2+1)已经不适用了。但是本来这种记录方式就是为了我们写起来方便(指针退散),所以解决起来也很简单,对于每个节点而言,我只需要强行记录下左右节点的编号,那么每个节点的编号无论怎么乱都是被他的父节点记录着的,所以无所谓,随便搞,但是为了方便,采用的是编号由1……n逐渐增大。
其实想法是很容易理解的,只要想通了,就能发现可持久化的这种思想还能利用在许多不同的数据结构上。
三、丑陋的模板
具体的用法隐藏在注释里。
#define LL long long int
const int maxn=100005;
int cnt;
int rt[maxn];
struct Node
{
int ls, rs, l, r;
LL sum;
}tr[maxn*33];
void PushUp(int i)
{
tr[i].sum = tr[tr[i].ls].sum + tr[tr[i].rs].sum;
}
int Build(int l,int r)
{
int pos = ++cnt;
if (l == r)
{
tr[pos].sum = 0;
return pos;
}
int mid = (l + r) >> 1;
tr[pos].ls = Build(l,mid);
tr[pos].rs = Build(++mid,r);
return pos;
}
//第零号版本的线段树
LL Query(int ed,int l,int r,int ql,int qr)
{ //ed是 版本号
if (ql<=l && r<=qr) return tr[ed].sum;
int mid = (l + r) >> 1;
if (qr <= mid) return Query(tr[ed].ls,l,mid,ql,qr);
else if(ql>mid)return Query(tr[ed].rs,mid+1,r,ql,qr);
else
{
return Query(tr[ed].ls,l,mid,ql,mid)+Query(tr[ed].rs,mid+1,r,mid+1,qr);
}
}
//查询第ed个版本的线段树[ql,qr]上的和
int update(int ed,int l,int r,int p,LL k)
{
int pos = ++cnt;
tr[pos].sum=tr[ed].sum;
if (l == r)
{
tr[pos].sum += k;
return pos;
}
tr[pos].ls = tr[ed].ls;
tr[pos].rs = tr[ed].rs;
int mid = (l + r) >> 1;
if (p <= mid) tr[pos].ls = update(tr[ed].ls,l,mid,p,k);
else tr[pos].rs = update(tr[ed].rs,++mid,r,p,k);
PushUp(pos);
return pos;
}
//单点更新p点的值加上k
//返回值为新的版本树的根节点
int main()
{
rt[0]=Build(1,maxn);//建树的范围可随题意变化
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int p;
LL w;
scanf("%d %lld",&p,&w);
rt[i]=update(rt[i-1],1,maxn,p,w);//注意!更新新的版本需要记录新的根
}
int Q;
scanf("%d",&Q);
while(Q--)
{
int b;
int l,r;
scanf("%d %d %d",&b,&l,&r);
printf("%lld\n",Query(rt[b],1,maxn,l,r));//查询操作
//查询第b个版本的线段树上的信息
}
}