zoukankan      html  css  js  c++  java
  • 线段树

    原理

    (注:由于线段树的每个节点代表一个区间,以下叙述中不区分节点和区间,只是根据语境需要,选择合适的词)
    线段树本质上是维护下标为1,2,..,n的n个按顺序排列的数的信息
    所以 其实是“点树” 是维护n的点的信息 至于每个点的数据的含义可以有很多
    在对线段操作的线段树中 每个点代表一条线段 在用线段树维护数列信息的时候 每个点代表一个数 但本质上都是每个点代表一个数
    以下 在讨论线段树的时候 区间[L,R]指的是下标从L到R的这(R-L+1)个数 而不是指一条连续的线段
    只是有时候这些数代表实际上一条线段的统计结果而已
    线段树是将每个区间[L,R]分解成[L,M]和[M+1,R] (其中M=(L+R)/2 这里的除法是整数除法,即对结果下取整)直到 L==R 为止
    开始时是区间[1,n]  通过递归来逐步分解 假设根的高度为1的话 树的最大高度为(n>1)
    线段树对于每个n的分解是唯一的 所以n相同的线段树结构相同
    下图展示了区间[1,13]的分解过程:

    上图中 每个区间都是一个节点 每个节点存自己对应的区间的统计信息

    代码

    单点修改

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<cstring>
     4 #include<cmath>
     5 #include<string>
     6 #include<algorithm>
     7 #include<map>
     8 #include<queue>
     9 #include<stack>
    10 #include<set>
    11 #include<vector>
    12 using namespace std;
    13 typedef long long ll;
    14 const int maxn = 5e5 + 5; 
    15 int l[4 * maxn], r[4 * maxn];
    16 ll sum[4 * maxn];
    17 void build(int L, int R, int now)//输入的时候L是1,R是数列长度,now == 1 
    18 {
    19     l[now] = L; r[now] = R;
    20     if(L == R)
    21     {
    22         /* 递归边界:左右端点重合,说明此时区间里只有一个元
    23         素,正好就可以读入数据,而且此时读入的也正好是该区间
    24         的区间和 */ 
    25         scanf("%lld", &sum[now]);
    26         return ;
    27     }
    28     int mid = (L + R) >> 1;
    29     build(L, mid, now << 1);    //构建左子树 
    30     build(mid + 1, R, now << 1 | 1);//构建右子树,注意从 mid + 1开始 
    31     sum[now] = sum[now << 1] + sum[now << 1 | 1];
    32     // 该区间和就等于左右子区间和的加和 
    33 }
    34 
    35 void update(int idx, int d, int now){//idx为输入数列中的序号, d为增加值, now == 1 
    36     //idx 代表结点编号,d 代表这个数加上 d 
    37     sum[now] += d;
    38     if (l[now] == r[now]) return;    //到达叶结点了 
    39     int mid = (l[now] + r[now]) >> 1;
    40     //然后判断要更新的点在左还是右子树中
    41     if (idx <= mid) update(idx, d, now << 1);
    42     else update(idx, d, now << 1 | 1);
    43 }
    44 
    45 
    46 ll query(int L, int R, int now){//L R表示区间[L, R], now == 1, 函数返回值为区间和 
    47     if (L == l[now] && R == r[now]) return sum[now];
    48     int mid = (l[now] + r[now]) >> 1;
    49     if (R <= mid) return query(L, R, now << 1);
    50     else if (L > mid) return query(L, R, now << 1 | 1);
    51     else return query(L, mid, now << 1) + query(mid + 1, R, now << 1 | 1);
    52     //没什么好解释的吧,很好理解 
    53 }
    54 
    55 int main() 
    56 {
    57      int n, q; scanf("%d%d", &n, &q);
    58      build(1, n, 1);
    59      while(q--)
    60      {
    61          int op, a, b; scanf("%d%d%d", &op, &a, &b);
    62          if (op == 1) update(a, b - query(a, a, 1), 1);
    63          else printf("%lld
    ", query(a, b, 1));
    64       }
    65       return 0;
    66 }
    单点修改

    区间修改

    1.区间加值

     1 //区间加值 
     2 #include<cstdio>
     3 #include<iostream>
     4 #include<cstring>
     5 #include<cmath>
     6 #include<string>
     7 #include<algorithm>
     8 #include<map>
     9 #include<queue>
    10 #include<stack>
    11 #include<set>
    12 #include<vector>
    13 typedef long long ll;
    14 using namespace std;
    15 void pushup(int o) {
    16               //pushup函数,该函数本身是将当前结点用左右子节点的信息更新,此处求区间和,用于update中将结点信息传递完返回后更新父节点
    17     st[o] = st[o<<1] + st[o<<1|1];
    18 }
    19 
    20 void pushdown(int o,int l,int r) {
    21       //pushdown函数,将o结点的信息传递到左右子节点上
    22     if(add[o]) {
    23                      //当父节点有更新信息时才向下传递信息
    24         add[o<<1] += add[o];
    25               //左右儿子结点均加上父节点的更新值
    26         add[o<<1|1]+=add[o];
    27         int m=l+((r-l)>>1);
    28         st[o<<1]+=add[o]*(m-l+1);
    29           //左右儿子结点均按照需要加的值总和更新结点信息
    30         st[o<<1|1]+=add[o]*(r-m);
    31         add[o]=0;
    32                         //信息传递完之后就可以将父节点的更新信息删除
    33     }
    34 }
    35 
    36 void update(int o,int l,int r,int ql,int qr,int addv) {
    37       //ql、qr为需要更新的区间左右端点,addv为需要增加的值
    38     if(ql<=l&&qr>=r) {
    39                               //与单点更新一样,当当前结点被需要更新的区间覆盖时
    40         add[o]+=addv;
    41                               //更新该结点的所需更新信息
    42         st[o]+=addv*(r-l+1);
    43                         //更新该结点信息
    44         return;
    45                             //根据lazy思想,由于不需要遍历到下层结点,因此不需要继续向下更新,直接返回
    46     }
    47 
    48     pushdown(o,l,r);
    49                       //将当前结点的所需更新信息传递到下一层(其左右儿子结点)
    50     int m=l+((r-l)>>1);
    51     if(ql<=m)update(o<<1,l,m,ql,qr,addv);
    52          //当需更新区间在当前结点的左儿子结点内,则更新左儿子结点
    53     if(qr>=m+1)update(o<<1|1,m+1,r,ql,qr,addv);
    54        //当需更新区间在当前结点的右儿子结点内,则更新右儿子结点
    55     pushup(o);
    56                       //递归回上层时一步一步更新回父节点
    57 }
    58 
    59 ll query(int o,int l,int r,int ql,int qr) {
    60         //ql、qr为需要查询的区间
    61     if(ql<=l&&qr>=r) return st[o];
    62           //若当前结点覆盖区间即为需要查询的区间,则直接返回当前结点的信息
    63     pushdown(o,l,r);
    64                       //将当前结点的更新信息传递给其左右子节点
    65     int m=l+((r-l)>>1);
    66     ll ans=0;
    67                           //所需查询的结果
    68     if(ql<=m)ans+=query(o<<1,l,m,ql,qr);
    69          //若所需查询的区间与当前结点的左子节点有交集,则结果加上查询其左子节点的结果
    70     if(qr>=m+1)ans+=query(o<<1|1,m+1,r,ql,qr);
    71      //若所需查询的区间与当前结点的右子节点有交集,则结果加上查询其右子节点的结果
    72        return ans;
    73 }

    2.区间改值

     1 // 区间改值(其实只有pushdow函数和update中修改部分与区间加值不同) 
     2 #include<cstdio>
     3 #include<iostream>
     4 #include<cstring>
     5 #include<cmath>
     6 #include<string>
     7 #include<algorithm>
     8 #include<map>
     9 #include<queue>
    10 #include<stack>
    11 #include<set>
    12 #include<vector>
    13 typedef long long ll;
    14 using namespace std;
    15 void pushup(int o){
    16      st[o]=st[o<<1]+st[o<<1|1];
    17  }
    18  
    19  void pushdown(int o,int l,int r){  //pushdown和区间加值不同,改值时修改结点信息只需要对修改后的信息求和即可,不用加上原信息
    20      if(change[o]){
    21          int c=change[o];
    22          change[o<<1]=c;
    23          change[o<<1|1]=c;
    24          int m=l+((r-l)>>1);
    25          st[o<<1]=(m-l+1)*c;
    26          st[o<<1|1]=(r-m)*c;
    27          change[o]=0;
    28      }
    29  }
    30  
    31  void update(int o,int l,int r,int ql,int qr,int c){
    32      if(ql<=l&&qr>=r){         //同样更新结点信息和区间加值不同
    33          change[o]=c;
    34          st[o]=(r-l+1)*c;
    35          return;
    36      }
    37      
    38      pushdown(o,l,r);
    39      int m=l+((r-l)>>1);
    40      if(ql<=m)update(o<<1,l,m,ql,qr,c);
    41      if(qr>=m+1)update(o<<1|1,m+1,r,ql,qr,c);
    42      pushup(o);
    43  }
    44  
    45  int query(int o,int l,int r,int ql,int qr){
    46      if(ql<=l&&qr>=r) return st[o];
    47      pushdown(o,l,r);
    48      int m=l+((r-l)>>1);
    49      int ans=0;
    50      if(ql<=m)ans+=query(o<<1,l,m,ql,qr);
    51      if(qr>=m+1)ans+=query(o<<1|1,m+1,r,ql,qr);
    52      return ans;
    53  }
  • 相关阅读:
    团队项目-Beta冲刺(第一周)
    个人第4次作业—Alpha项目测试
    团队项目——Alpha发布2
    优课堂考勤系统——Alpha发布1
    优课堂—系统设计
    优课堂—需求分析
    thrift学习笔记
    APP分享
    ActionBar
    ListView添加headview
  • 原文地址:https://www.cnblogs.com/thx666/p/9188680.html
Copyright © 2011-2022 走看看