zoukankan      html  css  js  c++  java
  • 洛谷——P3919 【模板】可持久化数组(可持久化线段树/平衡树)

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

    题目背景

    UPDATE : 最后一个点时间空间已经放大

    标题即题意

    有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集)

    题目描述

    如题,你需要维护这样的一个长度为 $N$ 的数组,支持如下几种操作

    1. 在某个历史版本上修改某一个位置上的值

    2. 访问某个历史版本上的某一位置的值

    此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)

    输入输出格式

    输入格式:

    输入的第一行包含两个正整数 $N$,$M$, 分别表示数组的长度和操作的个数。

    第二行包含NN个整数,依次为初始状态下数组各位的值(依次为 $a_i$,$1 leq i leq N$)。

    接下来MM行每行包含3或4个整数,代表两种操作之一(ii为基于的历史版本号):

    1. 对于操作1,格式为$v_i 1 {loc}_i {value}_i$,即为在版本$v_i$的基础上,将 $a_{{loc}_i}$ 修改为 ${value}_i$

    2. 对于操作2,格式为$v_i 2 {loc}_i$,即访问版本$v_i$中的 $a_{{loc}_i}$的值

    输出格式:

    输出包含若干行,依次为每个操作2的结果。

    输入输出样例

    输入样例#1: 复制
    5 10
    59 46 14 87 41
    0 2 1
    0 1 1 14
    0 1 1 57
    0 1 1 88
    4 2 4
    0 2 5
    0 2 4
    4 2 1
    2 2 2
    1 1 5 91
    输出样例#1: 复制
    59
    87
    41
    87
    88
    46

    说明

    数据规模:

    对于30%的数据:$1 leq N$, $M leq {10}^3$

    对于50%的数据:$1 leq N$, $M leq {10}^4$

    对于70%的数据:$1 leq N$, $M leq {10}^5$

    对于100%的数据:$1 leq N$, $M leq {10}^6$, $1 leq {loc}_i$ $leq N$, $0 leq v_i < i$, $-{10}^9 leq a_i$, ${value}_i leq {10}^9$

    经测试,正常常数的可持久化数组可以通过,请各位放心

    数据略微凶残,请注意常数不要过大

    另,此题I/O量较大,如果实在TLE请注意I/O优化

    询问生成的版本是指你访问的那个版本的复制

    样例说明:

    一共11个版本,编号从0-10,依次为:

    * 0 : 59 46 14 87 41

    * 1 : 59 46 14 87 41

    * 2 : 14 46 14 87 41

    * 3 : 57 46 14 87 41

    * 4 : 88 46 14 87 41

    * 5 : 88 46 14 87 41

    * 6 : 59 46 14 87 41

    * 7 : 59 46 14 87 41

    * 8 : 88 46 14 87 41

    * 9 : 14 46 14 87 41

    * 10 : 59 46 14 87 91

    ~(≧▽≦)/~啦啦啦 

    身为蒟蒻的我终于入门了可持久化线段树的冰山一角,开森

    可持久化线段树总结(可持久化线段树,线段树)

    %%FlashHu%%,可以直接转到这位大佬的博客学习

    大佬可直接转主席树乱讲

    刚入门的蒟蒻对此的理解:

    可持久化线段树,支持访问历史版本,修改历史版本,emmmmm

    显然,你对于每一次版本都新建一颗线段树,会TLE,更会MLE,这种方法行不通

    观察发现,当你修改一个历史版本时,即创建一个新版本,当前版本相对于历史版本改变的仅仅是那个点所影响的那条链,

    emmmmm貌似就这么多了

    $get$到新技能,非递归版线段树

    IL void insert(int *T,int u,int l,int r,int k){
    //T当前节点指针,u原版本对应节点,区间端点,所要修改位置
        while(l!=r){
            int mid=(l+r)>>1;
            *T=++tot;//新增节点,分配空间
            if(k<=mid) r=mid,rc[*T]=rc[u],T=&lc[*T],u=lc[u];//如果所要修改的节点在区间中点mid左侧,说明右侧线段树没有改变,T,u更新为当前左子树,右子树连接到历史版本好了,继续
            else l=mid+1,lc[*T]=lc[u],T=&rc[*T],u=rc[u];//上同
        }
        in(val[*T=++tot]);
    }

    奉上代码:

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    
    #define N 10000000
    #define IL inline
    using namespace std;
    
    IL void in(int &x){
        register char c=getchar();x=0;int f=1;
        for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    
    int rt[N],lc[N],rc[N],tot,val[N];
    
    IL void build(int &RT,int l,int r){
        RT=++tot;
        if(l==r) {in(val[tot]);return;}
        int mid=(l+r)>>1;
        build(lc[RT],l,mid);
        build(rc[RT],mid+1,r);
    }
    IL void insert(int *T,int u,int l,int r,int k){
        while(l!=r){
            int mid=(l+r)>>1;
            *T=++tot;
            if(k<=mid) r=mid,rc[*T]=rc[u],T=&lc[*T],u=lc[u];
            else l=mid+1,lc[*T]=lc[u],T=&rc[*T],u=rc[u];
        }
        in(val[*T=++tot]);
    }
    IL int ask(int t,int l,int r,int k){
        while(l!=r){
            int m=(l+r)>>1;
            if(k<=m) r=m,t=lc[t];
            else l=m+1,t=rc[t];
        }
        return val[t];
    }
    
    int n,m;
    
    int main()
    {
        in(n),in(m);
        build(rt[0],1,n);
        for(int opt,v,loc,i=1;i<=m;i++){
            in(v),in(opt),in(loc);
            if(opt&1) insert(&rt[i],rt[v],1,n,loc);
            else{
                printf("%d
    ",ask(rt[v],1,n,loc));
                rt[i]=++tot;
                lc[tot]=lc[rt[v]];
                rc[tot]=rc[rt[v]];
            }
        }
        
        return 0;
    }

     emmm,来一发递归版的

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    
    #define N 100000000
    using namespace std;
    
    int rt[N],P,lc[N],rc[N],val[N];
    
    void build(int &R,int l,int r){
        R=++P;
        if(l==r) {scanf("%d",&val[P]);return;}
        int mid=(l+r)>>1;
        build(lc[R],l,mid);
        build(rc[R],mid+1,r);
    }
    
    void insert(int *T,int u,int l,int r,int k){
        *T=++P;
        if(l==r) {scanf("%d",&val[*T]);return;}
        int mid=(l+r)>>1;
        if(k<=mid) rc[*T]=rc[u],insert(&lc[*T],lc[u],l,mid,k);
        else lc[*T]=lc[u],insert(&rc[*T],rc[u],mid+1,r,k);
    }
    
    int ask(int t,int l,int r,int k){
        int mid=(l+r)>>1;
        if(l==r) return val[t];
        if(k<=mid) return ask(lc[t],l,mid,k);
        else return ask(rc[t],mid+1,r,k);
    }
    
    int n,m;
    
    int main()
    {
        scanf("%d%d",&n,&m);
        build(rt[0],1,n);
        for(int opt,v,loc,i=1;i<=m;i++){
            scanf("%d%d%d",&v,&opt,&loc);
            if(opt==1) insert(&rt[i],rt[v],1,n,loc);
            else printf("%d
    ",ask(rt[v],1,n,loc)),rt[i]=++P,lc[rt[i]]=lc[rt[v]],rc[rt[i]]=rc[rt[v]];
        }
        
        return 0;
    }
    主席树入门

    以上是主席树单点修改+单点查询的模板。

    主席树才A了这一道题,太弱了吧,垃圾(我)

    SP11470 TTM - To the moon

    在大佬博客发现的一道题(额,又是一道模板题)

    主席树区间修改+区间查询模板

    主要是理解线段树标记永久化的思想 线段树标记永久化 <-可以转这位大佬的博客学习

    线段树标记永久化,当你所要修改的区间包含于当前区间时,就说明所要修改区间对当前区间产生了贡献,那么当前区间直接加上这个贡献即可,只有当当前区间完全符合修改区间时,

    就下方标记(不,其实是添加标记,因为不需要$pushup$),这就是区间修改

    IL void Modify(int &t,int u,int l,int r,int v){
        t=++tot;//添加新节点 
        lc[t]=lc[u],rc[t]=rc[u],cov[t]=cov[u],sum[t]=sum[u];//更新新节点 
        sum[t]+=v*(qr-ql+1);//对此区间产生贡献,此区间直接加上此贡献 
        if(ql==l&&qr==r) {cov[t]+=v;return;}
        int mid=(l+r)>>1;
        if(qr<=mid) Modify(lc[t],lc[u],l,mid,v);
        else if(ql>mid) Modify(rc[t],rc[u],mid+1,r,v);
        else Modify(lc[t],lc[u],l,mid,ql,mid),Modify(rc[t],rc[u],mid+1,r,mid+1,qr);
        //若所修改区间qr在mid左侧,直接转到左子树, 
        //若所修改区间ql在mid右侧,直接转到右子树, 
        //反之,同时转向左右子树 
    }

     接下来考虑区间查询,由上到下,累加标记,若当前区间有标记,那么对查询区间肯定有影响,要累加这个标记,

    最后在加上所查询区间的$sum$值即可。

    LL query(LL t,LL l,LL r,LL ql,LL qr){
        if(l==ql&&r==qr) return sum[t];
        int mid=(l+r)>>1;
        LL res=cov[t]*(qr-ql+1);
        if(qr<=mid) res+=query(lc[t],l,mid,ql,qr);
        else if(ql>mid) res+=query(rc[t],mid+1,r,ql,qr);
        else res+=query(lc[t],l,mid,ql,mid),res+=query(rc[t],mid+1,r,mid+1,qr);
        return res;
    }

    完整代码:

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    
    #define N 10000000
    #define IL inline 
    #define LL long long
    using namespace std;
    void in(LL &x){
        register char c=getchar();x=0;LL f=1;
        while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
        while(isdigit(c)){x=x*10+c-'0';c=getchar();}
        x*=f;
    }
    
    LL rt[N],lc[N],rc[N],tot,sum[N],cov[N];
    
    IL void build(LL &t,LL l,LL r){
        t=++tot;
        if(l==r){in(sum[t]);return;}
        LL m=(l+r)>>1;
        build(lc[t],l,m);
        build(rc[t],m+1,r);
        sum[t]+=sum[lc[t]]+sum[rc[t]];
    }
    IL void Modify(LL &t,LL u,LL l,LL r,LL ql,LL qr,LL v){
        t=++tot;
        lc[t]=lc[u],rc[t]=rc[u],cov[t]=cov[u],sum[t]=sum[u];
        sum[t]+=v*(qr-ql+1);
        if(ql==l&&qr==r) {cov[t]+=v;return;}
        LL mid=(l+r)>>1;
        if(qr<=mid) Modify(lc[t],lc[u],l,mid,ql,qr,v);
        else if(ql>mid) Modify(rc[t],rc[u],mid+1,r,ql,qr,v);
        else Modify(lc[t],lc[u],l,mid,ql,mid,v),Modify(rc[t],rc[u],mid+1,r,mid+1,qr,v);
    }
    LL query(LL t,LL l,LL r,LL ql,LL qr){
        if(l==ql&&r==qr) return sum[t];
        int mid=(l+r)>>1;
        LL res=cov[t]*(qr-ql+1);
        if(qr<=mid) res+=query(lc[t],l,mid,ql,qr);
        else if(ql>mid) res+=query(rc[t],mid+1,r,ql,qr);
        else res+=query(lc[t],l,mid,ql,mid),res+=query(rc[t],mid+1,r,mid+1,qr);
        return res;
    }
    
    LL n,m;
    
    int main()
    {
        in(n),in(m);
        build(rt[0],1,n);
        LL T=0;
        for(LL l,r,d,i=1;i<=m;i++){
            char x;
            cin>>x;
            if(x=='B'){
                in(d);
                T=d;
                tot=rt[T+1]-1;//回收空间
            }else{
                in(l),in(r);
                if(x=='C') in(d),++T,Modify(rt[T],rt[T-1],1,n,l,r,d);
                if(x=='Q') printf("%lld
    ",query(rt[T],1,n,l,r));
                if(x=='H') in(d),printf("%lld
    ",query(rt[d],1,n,l,r));
            }
        }
        
        return 0;
    }

    只会抄题解,抄完之后还镇定自若地跟你们瞎$BB$地蒟蒻。qwq。。。

    主席树总结(经典区间第k小问题)(主席树,线段树)(下一道模板题)

    关于主席树的入门,讲解和题单

  • 相关阅读:
    Android studio快捷键大全 和 eclipse对照(原)
    .net 提取注释生成API文档 帮助文档
    查看443端口被占用无法启动解决办法
    关于正则表达式 C#
    关于 ImageLoader 说的够细了。。。
    什么时候用Application的Context,什么时候用Activity的Context
    关于layoutparam 请铭记。。。。
    java 静态方法上的泛型
    让多个Fragment 切换时不重新实例化
    开源.net 混淆器ConfuserEx介绍
  • 原文地址:https://www.cnblogs.com/song-/p/9763239.html
Copyright © 2011-2022 走看看