线段树的升级版——可持久化线段树

可持久化线段树,听起来像线段树的升级版。但是这个东西有什么用呢?
一道例题:

最大值加强版

在N(1<=N<=100000)个数A1…An组成的序列上进行M(1<=M<=100000)次操作,操作有两种:
1 x y:表示修改A[x]为y。
2 k x y:询问第k次修改后x到y之间的最大值。

这原本是一道线段树的入门题,但是增加了“第k次修改后的”这个限制条件,辣鸡的线段树就不可做了。我们发现“第k次修改后”实质上是要求记录历史的版本,即每一次修改后的版本。
一个very暴力的想法是每次修改都新建一棵线段树。
但是我们分析一下,时间复杂度 O(mnlog2n) ,空间复杂度 O(mn) ,完美爆炸。
注意到,每次修改后变化的东西很少,只有 log2n 个。所以重新新建多了许多冗余状态。
对于可持久化线段树的修改操作,值改变的那些点,新建;不变的点,直接指过去。记录每次修改后线段树的根,这样时间复杂度 O(mlog2n) ,空间复杂度 O(mlog2n) ,可以承受。
线段树的升级版——可持久化线段树_第1张图片

最大值加强版加强版

在N(1<=N<=100000)个数A1…An组成的序列上进行M(1<=M<=100000)次操作,操作有两种:
(1)1 L R C:表示把A[L]到A[R]增加C(C的绝对值不超过10000);
(2)2 L R:询问第k次修改后A[L]到A[R]之间的最大值。

这题只是将上面的单点修改变成了区间修改。对于普通线段树我们直接打lazy标记。
可持久化的线段树怎么打lazy标记呢?
设当前的点为v,左儿子为lv,右儿子为rv。
若v被修改区间完全包含,那么v内的所有点的值都会变。那么就想普通线段树一样,给当前点v打上标记。对于lv和rv,各新建一个点。注意这棵线段树当前新建的点的各项信息要把与之对应的上一棵线段树的点的信息搬过来。大体长得和普通线段树差不多。

Key Code

这是标记下传的代码,也是可持久化线段树区间修改的精髓。

void down(int v)
{
    if(!tree[v].ad) return;
    tree[++m]=tree[tree[v].l],tree[m].mx+=tree[v].ad,tree[m].ad+=tree[v].ad;
    tree[v].l=m;
    tree[++m]=tree[tree[v].r],tree[m].mx+=tree[v].ad,tree[m].ad+=tree[v].ad;
    tree[v].r=m;
    tree[v].ad=0;
}

你可能感兴趣的:(线段树,可持久化线段树)