可持久化线段树学习笔记

可持久化,即对数据修改后仍可查询到其历史版本。

以模板题为例:

P3919 【模板】可持久化线段树 1(可持久化数组)

  单点修改、查询的可持久化。

  暴力时空复杂度:O(nm(版本复制)+m(修改查询)),可持久化线段树 时空复杂度只为:O(m log n+n)

  题解口胡:

    建一个数组hed存各版本的对应的线段树根

    对于修改操作:对修改了的部分进行新建,没有修改的部分共用。因修改而新建的部分不只有叶子节点,还应有其到根节点的路径,否则无法维护线段树的结构。

    对于查询操作:使hed[当前版本数]=hed[查询版本数],查询就完事了。(当时还建个新根,连原版本的左右儿子。这都是多此一举)

  数组大小(本题单点修改):4*n(一棵线段树(因为用了动态开点,实际一棵线段树的空间占用要更少))+m (ceil(log n) +1)(长度为n的区间的线段树最大深度为ceil(log n)+1

  踩过的坑:

    调用修改或查询函数里表区间的l,r形参要为1,n,结果直接写成l和r了,没注意意义。

    查询函数里面的递归原树上的点要跟着走

模板题AC代码:

#include<iostream>
#include<cstdio>

using namespace std;

const int N=1e6+6;

int n,m,hed[N],num[N*24],ls[N*24],rs[N*24],cnt;

inline int read()
{
    int x=0;
    bool f=0;
    char ch=getchar();
    while(!isdigit(ch))
        f|=ch=='-',ch=getchar();
    while(isdigit(ch))
        x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return f?-x:x;
}

void build(int t,int l,int r)
{
    if(l==r)
    {
        num[t]=read();
        return;
    }
    ls[t]=++cnt;
    build(cnt,l,(l+r)>>1);
    rs[t]=++cnt;
    build(cnt,((l+r)>>1)+1,r);
}

void modify(int u,int t,int l,int r,int w,int v)
{
    if(l==r)
    {
        num[u]=v;
        return;
    }
    if(w<=(l+r)>>1)
    {
        rs[u]=rs[t];
        ls[u]=++cnt;
        modify(cnt,ls[t],l,(l+r)>>1,w,v);
    }
    else
    {
        ls[u]=ls[t];
        rs[u]=++cnt;
        modify(cnt,rs[t],((l+r)>>1)+1,r,w,v);
    }
}

int fin(int t,int l,int r,int w)
{
    if(l==r)
        return num[t];
    if(w<=(l+r)>>1)
        return fin(ls[t],l,(l+r)>>1,w);
    else
        return fin(rs[t],((l+r)>>1)+1,r,w);
}

int main()
{
    n=read(),m=read();
    cnt=1;
    build(1,1,n);
    hed[0]=1;
    int t,ord,w,v;
    for(int i=1;i<=m;++i)
    {
        t=read(),ord=read();
        if(ord==1)
        {
            w=read(),v=read();
            hed[i]=++cnt;
            modify(cnt,hed[t],1,n,w,v);
        }
        else
        {
            w=read();
            hed[i]=hed[t];
            printf("%d\n",fin(hed[t],1,n,w));
        }
    }
    return 0;
}
AC代码

理解时可以以需求导向(暴力算法又慢又占空间)理解

核心:共用内存,尽可能少开点(只新建修改的节点)。核心也会适用于一些可持久化线段树以后的变式。

   一个操作处理完成后,以后这个操作对应的那棵线段树都不会再变了。(方便深入理解,但不可固化思维)

注意点:序列长度为n,操作数为m,单点修改情况下可持久化线段树空间应开:4*n+m (ceil(log n) +1)

应用:1、对数组可持久化简单维护:

    单点修改,简单区间修改(配合标记永久化,单次操作 新建节点数小于 4* (ceil(log n) +1)、时间复杂度O(logn))

   2、配合主席树

posted @ 2020-11-22 17:34  千叶繁华  阅读(115)  评论(0编辑  收藏  举报