• 线段树模板讲解


    洛谷题目链接:线段树

    线段树是一种用于区间修改查询的数据结构,可以支持的操作有单点修改区间查询,区间修改单点查询,区间修改区间查询等.

    线段树有递归版和结构体版,递归版在处理一开始没有赋初始值的问题时可以不用建树,而结构体版的则显得比较条理清晰.

    线段树比树状数组的代码复杂的多,但是树状数组多支持一个区间修改区间查询的操作,并且可以与其他的一些数据结构相适应(像是树链剖分等),也有很多的在此基础上的提高的算法,线面开始讲解一下思路.

    线段树操作如下:

    1. 输入点权,建树.
    2. 进行修改和查询.

    建树:

    在线段树中建树一般采用的是递归的方式建树,在建树的过程中保留每个点的信息(区间左端点,右端点,区间和等),在线段树中的每个节点就是一个区间.

    代码如下:

    void build(lol root,lol left,lol right){//节点,节点左端点,节点右端点
      if(left==right){//当节点左端点等于右端点时,即为叶子节点
        sum[root]=w[left];//叶子节点的区间和即为点权
        return;//这里记得要退出回溯
      }
      build(root*2,left,mid);//左子树
      build(root*2+1,mid+1,right);//右子树递归建树
      sum[root]=sum[ll(root)]+sum[rr(root)];//回溯时收集区间和
    }

    那么这样建好的树就会有这样的性质:

    1. 当前节点的左右儿子分别是root*2和root*2+1
    2. 当前节点包括的范围刚好是左右儿子的区间的并集

     所以之后在进行修改,查询等操作时会很方便.

    下移懒惰标记:

    这里提及了一个重要的操作:lazy[]数组,懒惰标记.

    lazy数组用于保存修改在某个区间上的值,但不即时修改赋值到节点上,而是在之后查询时需要查询它子树的值的时候再将懒惰标记下移.

    那么下移lazy标记就是将当前区间的修改细化到每个小区间上.具体细节见代码注释:

    void pushdown(int root,int left,int right){
      lazy[root*2]+=lazy[root];//左右子树加上上面节点的标记
      lazy[root*2+1]+=lazy[root];
      sum[root*2]+=lazy[root]*(mid-left+1);//将区间和加上子树的个数乘标记的大小
      sum[root*2+1]+=lazy[root]*(right-mid);
      lazy[root]=0;//消去节点的懒惰标记
    }

    为什么修改区间和时要将左子树乘上(mid-left+1)呢?因为左子树的左端点是left,右端点是mid,那么正好(mid-left+1)就是左子树的节点数,乘上lazy[root]的值也就是将它的子树每个加上lazy[root]的值.

    修改:

    修改时是寻找一个能全部包含于修改范围的区间,并将它打上lazy标记.

    一个需要修改的范围可以看做是一个个小的范围的集合,如:

    1到8的区间可以看做是[1,5]和[6,8];5到6的区间可以看做是[5,5]和[6,6];

    如果是将1~8加5,则将区间[1,5]和区间[6,8]节点的懒惰标记加上5就可以了.

    代码如下:

     1 void updata(int root,int left,int right,int l,int r,int val){
     2   if(l<=left&&right<=r){//如果找到一个能全部被包含于修改范围的区间
     3     lazy[root]+=val;//则加上懒惰标记
     4     sum[root]+=val*(right-left+1);//同时也要修改区间和,与pushdown的修改同理
     5     return;
     6   }
     7   if(lazy[root]) pushdown(root,left,right);//修改时也要将之前的懒惰标记下移
     8   if(l<=mid) updata(ll(root),left,mid,l,r,val);
     9   if(mid<r) updata(rr(root),mid+1,right,l,r,val);//递归寻找能全被包含的区间
    10   sum[root]=sum[ll(root)]+sum[rr(root)];
    11 }

    查询:

    查询和修改操作比较像,也是将一个大区间细化为一个个小区间,找能被完全包含的区间进行查询.

    下面直接上代码:

    1 lol query(int root,int left,int right,int l,int r){
    2   if(l<=left&&right<=r) return sum[root];//完全被包含的区间直接返回区间和
    3   if(r<left||right<l) return 0;//如果区间和查找区间没有交集,则直接返回0
    4   if(lazy[root]) pushdown(root,left,right);//这里要写在判断是否与查找区间有交集后面
    5   return query(ll(root),left,mid,l,r)+query(rr(root),mid+1,right,l,r);//递归查询
    6 }

    为什么要把懒惰标记下移的操作放在判断后呢?我们举个例子,假设已经递归到了叶子节点,如果先下移标记,就有可能导致数组的越界(向下移标记时左右子树的节点标号都是节点的两倍),所以要先进行判断区间的操作.

    几个简单的操作讲完了,下面放一个完整模板:

     1 #include<bits/stdc++.h>
     2 #define mid (left+right>>1)
     3 #define ll(x) (x<<1)
     4 #define rr(x) (x<<1|1)
     5 using namespace std;
     6 typedef long long lol;
     7 const int N=500000;
     8 
     9 lol n,m;
    10 lol w[N+10];
    11 lol sum[N+10];
    12 lol lazy[N+10];
    13 
    14 int gi(){
    15   int ans=0,f=1;char i=getchar();
    16   while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();}
    17   while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
    18   return ans*f;
    19 }
    20 
    21 void build(lol root,lol left,lol right){
    22   if(left==right){
    23     sum[root]=w[left];
    24     return;
    25   }
    26   build(ll(root),left,mid);
    27   build(rr(root),mid+1,right);
    28   sum[root]=sum[ll(root)]+sum[rr(root)];
    29 }
    30 
    31 void pushdown(int root,int left,int right){
    32   lazy[ll(root)]+=lazy[root];
    33   lazy[rr(root)]+=lazy[root];
    34   sum[ll(root)]+=lazy[root]*(mid-left+1);
    35   sum[rr(root)]+=lazy[root]*(right-mid);
    36   lazy[root]=0;
    37 }
    38 
    39 void updata(int root,int left,int right,int l,int r,int val){
    40   if(l<=left&&right<=r){
    41     lazy[root]+=val;
    42     sum[root]+=val*(right-left+1);
    43     return;
    44   }
    45   if(lazy[root]) pushdown(root,left,right);
    46   if(l<=mid) updata(ll(root),left,mid,l,r,val);
    47   if(mid<r) updata(rr(root),mid+1,right,l,r,val);
    48   sum[root]=sum[ll(root)]+sum[rr(root)];
    49 }
    50 
    51 lol query(int root,int left,int right,int l,int r){
    52   if(l<=left&&right<=r) return sum[root];
    53   if(r<left||right<l) return 0;
    54   if(lazy[root]) pushdown(root,left,right);
    55   return query(ll(root),left,mid,l,r)+query(rr(root),mid+1,right,l,r);
    56 }
    57 
    58 int main(){
    59   int x,y,val,flag;
    60   n=gi();m=gi();
    61   for(int i=1;i<=n;i++) w[i]=gi();
    62   build(1,1,n);
    63   for(int i=1;i<=m;i++){
    64     flag=gi();
    65     if(flag==1){
    66       x=gi();y=gi();val=gi();
    67       updata(1,1,n,x,y,val);
    68     }
    69     if(flag==2){
    70       x=gi();y=gi();
    71       printf("%lld
    ",query(1,1,n,x,y)); 
    72     }
    73   }
    74   return 0;
    75 }

     另外再贴一个结构体版的:

    #include<bits/stdc++.h>
    #define ll(x) (x<<1)
    #define rr(x) (x<<1|1)
    using namespace std;
    const int N=400000+5;
    typedef long long lol;
    
    lol n, m;
    lol w[N];
    
    struct seg_tree{
      lol sum, l, r, lazy;
    }t[N];
    
    lol gi(){
      lol ans = 0 , f = 1; char i=getchar();
      while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();}
      while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
      return ans * f;
    }
    
    void up(lol root){
      t[root].sum = t[ll(root)].sum + t[rr(root)].sum;
    }
    
    void build(lol root,lol l,lol r){
      int mid = l+r>>1;
      t[root].l = l , t[root].r = r;
      if(l == r){
        t[root].sum = w[l];
        return;
      }
      build(ll(root),l,mid);
      build(rr(root),mid+1,r);
      up(root);
    }
    
    void pushdown(lol root){
      lol mid = t[root].l + t[root].r >> 1;
      t[ll(root)].lazy += t[root].lazy;
      t[rr(root)].lazy += t[root].lazy;
      t[ll(root)].sum += t[root].lazy*(mid-t[root].l+1);
      t[rr(root)].sum += t[root].lazy*(t[root].r-mid);
      t[root].lazy = 0;
    }
    
    void updata(lol root,lol l,lol r,lol val){
      lol mid = t[root].l+t[root].r>>1;
      if(l<=t[root].l && t[root].r<=r){
        t[root].sum += val * (t[root].r-t[root].l+1);
        t[root].lazy += val;
        return;
      }
      if(t[root].lazy) pushdown(root);
      if(l <= mid) updata(ll(root),l,r,val);
      if(mid < r) updata(rr(root),l,r,val);
      up(root);
    }
    
    lol query(lol root,lol l,lol r){
      if(l<=t[root].l && t[root].r<=r) return t[root].sum;
      if(r<t[root].l || t[root].r<l) return 0;
      if(t[root].lazy) pushdown(root);
      return query(ll(root),l,r)+query(rr(root),l,r);
    }
    
    int main(){
      lol f, x, y, val; n = gi(); m = gi();
      for(lol i=1;i<=n;i++) w[i] = gi();
      build(1,1,n);
      for(lol i=1;i<=m;i++){
        f = gi(); x = gi(); y = gi();
        if(f == 1) val = gi() , updata(1,x,y,val);
        else printf("%lld
    ",query(1,x,y));
      }
      return 0;
    }
  • 相关阅读:
    java设计模式-适配器模式
    java设计模式-外观模式
    java设计模式-享元模式
    java设计模式-装饰模式
    java设计模式-组合模式
    java设计模式-桥接模式
    12月Java原生商城APP源码-完全开源
    uniapp插件市场-涂图视频编辑-美妆-剪辑-微整形原生sdk插件发布-优雅草科技
    12月最新仿知音漫画网站源码+手机端,小说漫画生成静态文件,超强负载量安全可靠
    如何把网易云音乐ncm格式转换成mp3格式---记一下
  • 原文地址:https://www.cnblogs.com/BCOI/p/8149181.html
Copyright © 2020-2023  润新知