zoukankan      html  css  js  c++  java
  • 可持久化线段树

      1.1可持久化

      顾名思义,数据的可持久化就是不仅能访问该文件的当前版本,也能访问该文件的历史版本,比较常见的应用就是撤销了,而本篇博文要写的就是线段树可持久化的实现。如果没学过线段树的话请先学习线段树以及线段树的动态开点

      1.2实现原理

      考虑最简单的情况:

                                           

      这是一棵非常简单的线段树,设为0号,现在,我们要改变点1的值,(即节点2),生成1号的历史版本,该怎么做呢?

      考虑到改变一个点,只要将该点和它所有的祖宗改一下就可以了,其他的节点不用变,如下所示:

                                        

      该图中,我们新增了5号节点代表点1的值,即虽然2号,5号的区间一样,但值不一样,新增了4号点作为新版本中的根节点,由于4号点的右孩子没改,所以没有新增节点,在新版本中代表区间2的节点依然是3号点。

      读者们也注意到了,图中新增了两个带箭头的数:0、1,它们代表了历史版本所指向的根节点,这样就能锁定该历史版本的整棵树了。

      那么,当树更复杂呢?我们可以考虑一下,将1号,4号节点视为一棵线段树的子树的根,而指向它们的是它们父亲节点,再将2、3节点视为1节点的子树,同样可以求解。

      2、代码实现

      这次总算有【模板】可持久化数组(可持久化线段树/平衡树)了,不用博主自己编题了……

      这可能要动态开点线段树的知识。

      该题中,我们只要实现单点修改,单点查询,我们需要两个数组记录该节点的左右儿子,若要修改的值在左儿子,那么该节点的右儿子就是模式版本的右儿子,再做左儿子就行了。

    void jia(long long mo,long long u,long long x,long long k)//mo:模式版本的代表该区间的节点,u:我们要构造的节点,x:区间位置,k:要修改成的值
    {
        l[u]=l[mo];
        r[u]=r[mo];
        if (l[u]==r[u])
        {
            z[u]=k;
            return;
        }//这个和build很像
        if (x<=(l[u]+r[u])/2)//x在左儿子
        {
            rr[u]=rr[mo];//右边和模式版本一样
            cnt++;
            ll[u]=cnt;
            jia(ll[mo],ll[u],x,k);
            z[u]=z[ll[u]]+z[rr[u]];
        }
        else
        {
            ll[u]=ll[mo];//左边就和模式版本一样
            cnt++;
            rr[u]=cnt;
            jia(rr[mo],rr[u],x,k);
            z[u]=z[ll[u]]+z[rr[u]];//x在右儿子
    } }

      单点修改算是可持久化的精髓,以下就是完整代码:

    #include<bits/stdc++.h>
    using namespace std;
    long long l[40000001],r[40000001],z[40000001],ll[40000001],rr[40000001],cnt,n,m,i,loc,v,val,q,a[1000001],he[1000001];//空间可不止4倍,博主比较懒,开了40倍。
    void build(long long u,long long l1,long long r1)
    {
        l[u]=l1;
        r[u]=r1;
        if (l1==r1)
        {
            z[u]=a[l1];
            return;
        }
        cnt++;
        ll[u]=cnt;//动态开点左区间
        build(ll[u],l1,(l1+r1)/2);
        cnt++;
        rr[u]=cnt;//动态开点右区间
        build(rr[u],(l1+r1)/2+1,r1);
        z[u]=z[ll[u]]+z[rr[u]];//其实这个用不到
    }
    void jia(long long mo,long long u,long long x,long long k)//单点修改
    {
        l[u]=l[mo];
        r[u]=r[mo];
        if (l[u]==r[u])
        {
            z[u]=k;
            return;
        }
        if (x<=(l[u]+r[u])/2)
        {
            rr[u]=rr[mo];
            cnt++;
            ll[u]=cnt;
            jia(ll[mo],ll[u],x,k);
            z[u]=z[ll[u]]+z[rr[u]];
        }
        else
        {
            ll[u]=ll[mo];
            cnt++;
            rr[u]=cnt;
            jia(rr[mo],rr[u],x,k);
            z[u]=z[ll[u]]+z[rr[u]];
        }
    }
    long long qui(long long u,long long x)//这和普通线段树的区别就是儿子一个是ll[u],rr[u],一个是u*2,u*2+1。
    {
        if (l[u]>x||r[u]<x) return 0;
        if (l[u]==r[u]) return z[u];
        else return qui(ll[u],x)+qui(rr[u],x);
    }
    int main()
    {
        he[0]=1;
        cnt=1;//其实这个也是要不要无所谓……
        scanf("%lld%lld",&n,&m);
        for (i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        build(1,1,n);
        for (i=1;i<=m;i++)
        {
            scanf("%lld%lld",&v,&q);;
            if (q==1)
            {
                cnt++;
                he[i]=cnt;//新增一个版本
                scanf("%lld%lld",&loc,&val);
                jia(he[v],he[i],loc,val);
            }
            else
            {
                scanf("%lld",&loc);
                he[i]=he[v];。。直接就将新版本指向模式版本的根节点
                printf("%lld
    ",qui(he[v],loc));
            }
        }
        return 0;
    }

      这道题还是相对简单的。接下来难的可就来了。

      3、进阶运用

      博主总算把这道题编好了:【模板】可持久化线段树(标准版),太难编了。

      在这道题中,我们发现从单点修改到了区间修改!我们对区间每一点赋值肯定是不现实的,所以我们就需要一些类似线段树的做法:

    void xiafang(long long u)//类似线段树的标记下传
    {
        cnt++;
        ll[u]=cnt;
        l[cnt]=l[u];
        r[cnt]=(l[u]+r[u])/2;
        z[cnt]=c[u]*(r[cnt]-l[cnt]+1);//实际上是新造了它的左、右儿子
        c[cnt]=c[u];
        cnt++;
        rr[u]=cnt;
        l[cnt]=(l[u]+r[u])/2+1;
        r[cnt]=r[u];
        z[cnt]=c[u]*(r[cnt]-l[cnt]+1);//标记也要下传
        c[cnt]=c[u];
        c[u]=0;
    }

      然后,就是区间加了:

    void jia(long long mo,long long u,long long l1,long long r1,long long k)
    {
        l[u]=l[mo];
        r[u]=r[mo];
        if (l[u]>=l1&&r[u]<=r1)//如果完全在赋值区间就不继续了
        {
            z[u]=k*(r[u]-l[u]+1);
            c[u]=k;//打上标记
            return;
        }
        if (c[mo]) xiafang(mo);
        if (r1<=(l[u]+r[u])/2)//在右边
        {
            rr[u]=rr[mo];
            cnt++;
            ll[u]=cnt;
            jia(ll[mo],ll[u],l1,r1,k);
            z[u]=z[ll[u]]+z[rr[u]];
        }
        else if (l1>(l[u]+r[u])/2)//在左边
        {
            ll[u]=ll[mo];
            cnt++;
            rr[u]=cnt;
            jia(rr[mo],rr[u],l1,r1,k);
            z[u]=z[ll[u]]+z[rr[u]];
        }
        else
        {
            cnt++;
            ll[u]=cnt;
            jia(ll[mo],ll[u],l1,r1,k);//两边都有
            cnt++;
            rr[u]=cnt;
            jia(rr[mo],rr[u],l1,r1,k);
            z[u]=z[ll[u]]+z[rr[u]];
        }
    }

      因为所有数字小于1e8,所以最大时不会爆long long的。

      以下是完整代码:

    #include<bits/stdc++.h>
    using namespace std;
    long long c[40000001],l[40000001],r[40000001],z[40000001],ll[40000001],rr[40000001],cnt,n,m,i,l1,r1,v,val,q,a[1000001],he[1000001];
    void xiafang(long long u)
    {
        cnt++;
        ll[u]=cnt;
        l[cnt]=l[u];
        r[cnt]=(l[u]+r[u])/2;
        z[cnt]=c[u]*(r[cnt]-l[cnt]+1);
        c[cnt]=c[u];
        cnt++;
        rr[u]=cnt;
        l[cnt]=(l[u]+r[u])/2+1;
        r[cnt]=r[u];
        z[cnt]=c[u]*(r[cnt]-l[cnt]+1);
        c[cnt]=c[u];
        c[u]=0;
    }
    void build(long long u,long long l1,long long r1)
    {
        l[u]=l1;
        r[u]=r1;
        if (l1==r1)
        {
            z[u]=a[l1];
            return;
        }
        cnt++;
        ll[u]=cnt;
        build(ll[u],l1,(l1+r1)/2);
        cnt++;
        rr[u]=cnt;
        build(rr[u],(l1+r1)/2+1,r1);
        z[u]=z[ll[u]]+z[rr[u]];
    }
    void jia(long long mo,long long u,long long l1,long long r1,long long k)
    {
        l[u]=l[mo];
        r[u]=r[mo];
        if (l[u]>=l1&&r[u]<=r1)
        {
            z[u]=k*(r[u]-l[u]+1);
            c[u]=k;
            return;
        }
        if (c[mo]) xiafang(mo);
        if (r1<=(l[u]+r[u])/2)
        {
            rr[u]=rr[mo];
            cnt++;
            ll[u]=cnt;
            jia(ll[mo],ll[u],l1,r1,k);
            z[u]=z[ll[u]]+z[rr[u]];
        }
        else if (l1>(l[u]+r[u])/2)
        {
            ll[u]=ll[mo];
            cnt++;
            rr[u]=cnt;
            jia(rr[mo],rr[u],l1,r1,k);
            z[u]=z[ll[u]]+z[rr[u]];
        }
        else
        {
            cnt++;
            ll[u]=cnt;
            jia(ll[mo],ll[u],l1,r1,k);
            cnt++;
            rr[u]=cnt;
            jia(rr[mo],rr[u],l1,r1,k);
            z[u]=z[ll[u]]+z[rr[u]];
        }
    }
    long long qui(long long u,long long l1,long long r1)
    {
        if (l[u]>r1||r[u]<l1) return 0;
        if (l[u]>=l1&&r[u]<=r1) return z[u];
        if (c[u]) xiafang(u);
        return (qui(ll[u],l1,r1)+qui(rr[u],l1,r1))%998244353;
    }
    int main()
    {
        he[0]=1;
        cnt=1;
        scanf("%lld%lld",&n,&m);
        for (i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        build(1,1,n);
        for (i=1;i<=m;i++)
        {
            scanf("%lld%lld",&v,&q);;
            if (q==1)
            {
                cnt++;
                he[i]=cnt;
                scanf("%lld%lld%lld",&l1,&r1,&val);
                jia(he[v],he[i],l1,r1,val);
            }
            else
            {
                scanf("%lld%lld",&l1,&r1);
                he[i]=he[v];
                printf("%lld
    ",qui(he[v],l1,r1)%998244353);
            }
        }
        return 0;
    }
  • 相关阅读:
    Java并发编程原理与实战九:synchronized的原理与使用
    Java并发编程原理与实战八:产生线程安全性问题原因(javap字节码分析)
    Java并发编程原理与实战七:线程带来的风险
    Java并发编程原理与实战六:主线程等待子线程解决方案
    Java并发编程原理与实战五:创建线程的多种方式
    Java并发编程原理与实战四:线程如何中断
    Java并发编程原理与实战三:多线程与多进程的联系以及上下文切换所导致资源浪费问题
    Java并发编程原理与实战二:并行&并发&多线程的理解
    ConcurrentHashMap 产生NullPointerException
    Java并发编程原理与实战一:聊聊并发
  • 原文地址:https://www.cnblogs.com/szbszb/p/11301467.html
Copyright © 2011-2022 走看看