zoukankan      html  css  js  c++  java
  • 【数据结构】线段树

    //Copyright © 2018 dudujerry
    //详细注释版线段树



    #include<cstring> #include<iostream> #include<cmath> using namespace std; typedef long long ll; struct tre{ ll w,f; }; tre tree[400002]; ll reSum; int cl,cr; ll a[100002]; void ad(int k,ll v,int l,int r){ //这个函数是"缺失版区间修改",也就是改变当前节点的和,但是不用真的改变下层元素 //也就是添加懒标记,这个函数也可以下传懒标记,因为下传懒标记相当于在下层重新创建了一个懒标记 tree[k].w+=(r-l+1)*v; //在理论上这个节点代表的区间中每个元素应该加上v,所以树上这个节点应该加上:区间元素个数(r-l+1)乘上v tree[k].f+=v; //但是实际上我们不需要真的对涉及到的树上区间元素进行修改, //所以我们只需要为这一层添加懒标记,意思是这个节点代表的区间和的区间每一个元素都加上v } void pushdown(int k,int l,int r){ //lazy标记下传 if(tree[k].f==0) return; //如果没有标记就返回 int mid=(l+r)/2; ad(k*2,tree[k].f,l,mid); //将标记传给左儿子 ad(k*2+1,tree[k].f,mid+1,r); //让标记传给右儿子 tree[k].f=0; //已经下传完了,清除本层标记,那么就是我们之前提到过的下传懒标记 } void build(int k,int l,int r){ if(l==r){ tree[k].w=a[l]; //到叶节点,将线段元素放到树里 return; } int mid=(l+r)/2; //中间元素为(l+r)/2,此后不表 build(k*2,l,mid); //转移到左儿子 build(k*2+1,mid+1,r); //转移到右儿子 tree[k].w=tree[k*2].w+tree[k*2+1].w; //递归完之后更新当前元素 } void change_sec(ll v,int k,int l,int r){ if(r<cl||l>cr){ //如果离开了要改变的范围,就离开 return; //一定要return } if(l>=cl&&r<=cr){ //如果全部进入范围 ad(k,v,l,r); //知道了树上编号,也知道了目前要修改的区间,就可以用"缺失版区间修改" return; //一定要return } pushdown(k,l,r); //凡是涉及到元素修改/查询,在递归前都要下传懒标记 int mid=(l+r)/2; change_sec(v,k*2,l,mid); change_sec(v,k*2+1,mid+1,r); tree[k].w=tree[k*2].w+tree[k*2+1].w; //凡是涉及到元素修改的操作,在递归完成后都要更新当前节点的值 } void ask_sec(int k,int l,int r){ if(r<cl||l>cr) return; if(l>=cl&&r<=cr){ reSum+=tree[k].w; //查询到了l到r的和 return; } pushdown(k,l,r); int mid=(l+r)/2; ask_sec(k*2,l,mid); ask_sec(k*2+1,mid+1,r); //没有改变值,所以不用更新 } int main(){ ios_base::sync_with_stdio(0); //读入优化 cin.tie(0); //加了这个就不能用printf和scanf了 int n,m; cin>>n>>m; for(int i=1;i<=n;i++){ cin>>a[i]; } build(1,1,n); for(int i=0;i<m;i++){ int N=0; cin>>N; if(N==1){ ll k; int x,y; cin>>x>>y>>k; //k代表区间修改要加上的数 cl=x; //用全局变量记录要修改的范围 cr=y; //同上 change_sec(k,1,1,n); //意思是:在cl,cr区间加上k,从树上编号1和其对应的区间1,n开始递归 } else{ int x,y; cin>>x>>y; reSum=0; //用全局变量记录查询结果 cl=x; //范围,记录要查询的范围 cr=y; ask_sec(1,1,n); //意思是:从树上编号1和其对应的区间1,n开始递归 cout<<reSum<<endl; } } return 0; }

     嗯...至于输入可以参考 https://www.luogu.org/problemnew/show/P3372

    这篇代码过了

    让我介绍一下线段树:

    各位,这就是一棵线段树。

    对于每个非叶节点,其左儿子右儿子分别维护它一半的区间。

    例如,a负责[1~8]区间,则其左儿子l负责[1~4],右儿子

    而我们需要给这些节点编号,设根编号为o,则左儿子编号为o*2,右儿子编号为o*2+1。

    这样,单点修改的时候只需要O(logn)的时间就可以完成。

    区间查询,类似分块的思想,对于非叶节点直接加上其维护的信息,剩余的叶节点则直接加上去。

    区间修改,同样运用分块的思想,但是如果完全与区间查询的做法一样,时间复杂度反而高于朴素做法。于是我们这里引入一个“懒标记”的概念。

    在修改到某完全属于目标区间的节点时(一个非叶节点),停止向下递归,同时加上它所维护区间长度*要修改的值,以及打上一个值为要修改的值懒标记。

    懒标记意味着,在这一层维护的信息中已经经过单位为“懒标记”的修改,下次遇到这个元素的时候要下传。

    然后,在访问到这个元素的时候进行“懒标记”的下传,相当于对它的儿子节点,或者说区间,进行修改,加上它所维护区间长度*要维护的值,以及打上一个值为要修改的值的懒标记。

    (在其它操作访问带有懒标记的元素时,就调用下传懒标记的函数,将懒标记下传一层。)

    比如区间查询,在路过某元素时发现懒标记,于是懒标记下传。然后区间修改向下递归的时候,继续下传,于是一直下传到叶节点。

    所以,我们就可以得到复杂度很低的区间修改。(线段树真是一种神奇的结构)

    0.几个优化技巧

    x/2用x>>1代替

    要查询/修改某个区间,不要把左右端点数值放在递归函数参数中,会压系统栈,比较慢。

    1.开数组需要开元素数量的4倍

    struct tre{
    
    ll w,f;
    
    };
    
    tre tree[4*MAXN];

     

    2.在ad(对单独编号的点进行lazy下传)中注意进行存储的值时需要(r-l+1)*v,v为下传的值

    void ad(int k,ll v,int l,int r){
    
      tree[k].w+=(r-l+1)*v;
    
      tree[k].f+=v;
    
    }
    

      

     

    3.pushdown,lazy下传只需要ad其左右儿子

    void pushdown(int k,int l,int r){
    
      if(tree[k].f==0)
    
        return; 
    
      int mid=(l+r)/2;
    
      ad(k*2,tree[k].f,l,mid);
    
      ad(k*2+1,tree[k].f,mid+1,r);
    
      tree[k].f=0;
    
    }
    

      

     

    4.build,change等涉及到区间修改的操作最后都要更新目前编号(例如求和)

    tree[k].w=tree[k*2].w+tree[k*2+1].w;
    

      

    5.判断区间的时候可以一开始判断,r<cl||l>cr(c*为要操作的区间);判断成功操作之后要return;

    if(r<cl||l>cr){
    
      return;
    
    }
    
    if(l>=cl&&r<=cr){
    
      ad(k,v,l,r);
    
      return;
    
    }
    

      

     

    6.修改和查询的时候都要调用pushdown

    pushdown(k,l,r);
    

      

    7.别忘了build!

    build(1,1,n);
    

      

    ---------------------------------------------------------------------------------------------------------------------------

  • 相关阅读:
    【Azure 应用服务】在Azure App Service多实例的情况下,如何在应用中通过代码获取到实例名(Instance ID)呢?
    【Azure 应用服务】App Service For Windows 中如何设置代理实现前端静态文件和后端Java Spring Boot Jar包
    【Azure Developer】使用Azure Key Vault 的Key签名后,离线验证的一些参考资料
    【Azure Function】调试 VS Code Javascript Function本地不能运行,报错 Value cannot be null. (Parameter 'provider')问题
    【Azure 应用服务】App Service 使用Tomcat运行Java应用,如何设置前端网页缓存的相应参数呢(Xms512m Xmx1204m)?
    【Azure API 管理】APIM添加Logtoeventhub的策略后,一些相关APIM与Event Hub的问题
    【Azure API 管理】为调用APIM的请求启用Trace 调试APIM Policy的利器
    【Azure 事件中心】China Azure上是否有Kafka服务简答
    【Azure 应用服务】探索在Azure上设置禁止任何人访问App Service的默认域名(Default URL)
    【Azure 微服务】记一次错误的更新Service Fabric 证书而引发的集群崩溃而只能重建
  • 原文地址:https://www.cnblogs.com/dudujerry/p/9850151.html
Copyright © 2011-2022 走看看