zoukankan      html  css  js  c++  java
  • 线段树模板 & 树状数组模板

    线段树的原理:将[1,n]分解成若干特定的子区间(数量不超过4*n),然后,将每个区间[L,R]都分解为少量特定的子区间,通过对这些少量子区间的修改或者统计,来实现快速对[L,R]的修改或者统计。
    作用:对编号连续的一些点的区间信息进行修改或者统计操作
    主要操作:区间查询、点更新、区间更新
    时间复杂度:修改和统计的复杂度都是O(log(N))

    由原理可以看出线段树维护的信息必须满足区间加法

    没有懒惰标记的线段树模板:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 
     4 const int maxn = 500005 * 4;    //线段树范围要开4倍
     5 
     6 struct Tree{
     7     int l, r, sum, maxx;
     8 };
     9 
    10 Tree node[maxn];        //node[maxn]为线段树处理数组
    11 int a[maxn];            //a[maxn]为原数组
    12 
    13 void PushUp(int i){
    14     node[i].sum = node[i << 1].sum + node[(i << 1) | 1].sum;
    15     node[i].maxx = max(node[i << 1].maxx, node[(i << 1) | 1].maxx);
    16 }
    17 
    18 void build(int i, int l, int r){
    19     node[i].l = l; node[i].r = r;
    20     if (l == r){
    21         node[i].maxx = a[l];
    22         node[i].sum = a[l];
    23         return;
    24     }
    25     int mid = (l + r) / 2;
    26     build(i << 1, l, mid);
    27     build((i << 1) | 1, mid + 1, r);
    28     PushUp(i);
    29 }
    30 
    31 int getsum(int i, int l, int r){
    32     if (node[i].l == l&&node[i].r == r)
    33         return node[i].sum;
    34     int mid = (node[i].l + node[i].r) / 2;
    35     if (r <= mid) return getsum(i << 1, l, r);
    36     else if (l > mid) return getsum((i << 1) | 1, l, r);
    37     else return getsum(i << 1, l, mid) + getsum((i << 1) | 1, mid + 1, r);
    38 }
    39 
    40 int getmax(int i, int l, int r){
    41     if (node[i].l == l&&node[i].r == r)
    42         return node[i].maxx;
    43     int mid = (node[i].l + node[i].r) / 2;
    44     if (r <= mid) return getmax(i << 1, l, r);
    45     else if (l>mid) return getmax((i << 1) | 1, l, r);
    46     else return max(getmax(i << 1, l, mid), getmax((i << 1) | 1, mid + 1, r));
    47 }
    48 
    49 void add(int i, int k, int v)            //当前更新的节点的编号为i(一般是1为初始编号,具体得看建立树时使用的第一个编号是什么)。{                                //k为需要更新的点的位置,v为修改的值的大小
    50     if (node[i].l == k&&node[i].r == k)        //左右端点均和k相等,说明找到了k所在的叶子节点
    51     {
    52         node[i].sum += v;
    53         node[i].maxx += v;
    54         return;    //找到了叶子节点就不需要在向下寻找了
    55     }
    56     int mid = (node[i].l + node[i].r) / 2;
    57     if (k <= mid) add(i << 1, k, v);
    58     else add((i << 1) | 1, k, v);
    59     PushUp(i);
    60 }
    View Code

    带有懒惰标记的线段树模板:

      1 const int N = 100005;
      2 typedef long long LL;
      3 LL a[N];                    //a[N]储存原数组
      4 LL  lazy[N << 2];            //空间应该开区间的四倍
      5     //!!!懒惰标记(在节点上标记该区间内的所有节点的增量,非必要时不必更新区间内点的sum,以减小时间复杂度 
      6 int n, q;
      7 
      8 struct Tree{
      9     int l, r;
     10     LL sum;//如果有其他的目标值比如max,需要在pushup中更新 
     11     int mid(){
     12         return (l + r) >> 1;
     13     }
     14 }tree[N<<2];        
     15 
     16 //更新节点信息 
     17 void PushUp(int rt){
     18             //节点rt 
     19     tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;
     20             //父节点的sum = 两个子节点sum之和 
     21 }
     22 
     23 //下推懒惰标记
     24 void PushDown(int rt,int m){
     25                 //节点rt,区间长度m 
     26     if(lazy[rt]){
     27         //下推懒惰标记 
     28         lazy[rt << 1] += lazy[rt];
     29         lazy[rt << 1 | 1] += lazy[rt];
     30         
     31         //向下更新sum值 
     32         tree[rt << 1].sum += lazy[rt] * (m - (m >> 1));
     33         tree[rt << 1 | 1].sum += lazy[rt] * (m >> 1);
     34         
     35         //重置“根”节点的懒惰标记 
     36         lazy[rt] = 0;
     37     }
     38 }
     39 
     40 //建树
     41 void build(int l, int r, int rt){
     42             //区间左端点l,区间右端点r,节点rt 
     43     tree[rt].l = l;
     44     tree[rt].r = r;
     45     lazy[rt] = 0;
     46     if (l == r)    {//当区间缩减到点时 赋予节点的sum 
     47         tree[rt].sum = a[l];
     48         return;
     49     }
     50     int m = tree[rt].mid();//tree[].mid()求区间中值函数 
     51     
     52     //构建左右子树 
     53     build(l, m, (rt << 1));
     54     build(m + 1, r, (rt << 1 | 1));
     55     
     56     //更新节点 
     57     PushUp(rt);
     58 }
     59 
     60 //区间修改:目标区间内每个点的sum都增加c 
     61 void update(LL c, int l, int r, int rt){
     62             //所有点增量c,目标区间左端点l,目标区间右端点r,节点rt 
     63             
     64 //如果刚好存在一个节点的区间与目标区间重合 
     65     if(tree[rt].l==l && tree[rt].r==r){
     66         
     67         //!!!懒惰标记(在节点上标记该区间内的所有节点的增量,非必要时不必更新区间内点的sum,以减小时间复杂度 
     68         lazy[rt] += c;
     69         tree[rt].sum += c*(r - l + 1);
     70         return;
     71     }
     72      
     73 //节点的区间并不能与目标区间重合 
     74     //如果已经下推到点(无论是否有懒惰标记,都不必更新sum,以减小时间复杂度 
     75     if(tree[rt].l == tree[rt].r)    return;
     76     
     77     int m = tree[rt].mid();
     78     //下推懒惰标记 
     79     PushDown(rt, tree[rt].r - tree[rt].l + 1);
     80     //如果目标区间的右端点  <=  节点区间的中值 
     81     if (r <= m)        update(c, l, r, rt << 1);//专门更新左子树 
     82     //如果目标区间的左端点  >  节点区间的中值 
     83     else if(l > m)    update(c, l, r, rt << 1 | 1);//专门个更新右子树 
     84     //如果目标区间涵盖节点区间的中值 
     85     else {
     86         update(c, l, m, rt << 1);
     87         update(c, m + 1, r, rt << 1 | 1);
     88     }
     89     PushUp(rt);
     90 }
     91  
     92 //求目标区间和 
     93 LL Query(int l, int r, int rt){
     94         //目标区间左端点l,目标区间右端点r,节点rt 
     95         
     96 //如果节点区间刚好和目标区间重合 
     97     if (l==tree[rt].l && r==tree[rt].r){
     98         return tree[rt].sum;
     99     }
    100     
    101 //节点区间不与目标区间重合 
    102     int m = tree[rt].mid();
    103     //为了求和需要下推懒惰标记,和更新一些必要节点的sum 
    104     PushDown(rt, tree[rt].r - tree[rt].l + 1);
    105     LL res = 0;
    106     //目标区间的右端点 <= 节点区间中值 
    107     if(r <= m)        res += Query(l, r, rt << 1);//向左缩减节点区间 
    108     //目标区间的左端点 > 节点区间的中值 
    109     else if(l > m)    res += Query(l, r, rt << 1 | 1);//向右缩减节点区间 
    110     //目标区间涵盖了节点区间的中值 
    111     else{
    112         res += Query(l, m, rt << 1);
    113         res += Query(m + 1, r, rt << 1 | 1);
    114     }
    115     return res;
    116 }
    View Code

    树状数组功能:

      计算 a1+a2+ …… +an = ?

      给定 i 和 x ,使得 ai += x

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 
     4 const int maxn = 50005;
     5 const int inf = 0x3f3f3f3f;
     6 int bit[maxn], n, a[maxn];
     7 
     8 //计算节点的父节点 
     9 int lowbit(int x){
    10     return x&(-x);//找到x的二进制的最后一个1 
    11 }
    12 
    13 //修改节点 
    14 void add(int i, int x){
    15     while(i <= n){
    16         bit[i] += x;
    17         i += lowbit(i); 
    18     }
    19 }
    20 void sub(int i, int x){
    21     while(i <= n){
    22         bit[i] -= x;
    23         i += lowbit(i);
    24     }
    25 }
    26 
    27 //求和
    28 int sum(int i){
    29     int s = 0;
    30     while(i > 0){
    31         s += bit[i];
    32         i -= lowbit(i);
    33     }
    34     
    35     return s;
    36 } 
    37 
    38 int main(){
    39     int t;
    40     scanf("%d", &t);
    41     for(int tt=1; tt<=t; ++tt){
    42         printf("Case %d:
    ", tt);
    43         scanf("%d", &n);
    44         memset(bit, 0, sizeof bit);
    45         for(int i=1; i<=n; ++i){
    46             scanf("%d", a+i);
    47             add(i, a[i]);
    48         }
    49         char str[10];
    50         while(scanf("%s", str) && str[0]!='E'){
    51             if(str[0] == 'Q'){
    52                 int u, v;
    53                 scanf("%d%d", &u, &v);
    54                 printf("%d
    ", sum(v) - sum(u-1));
    55             }
    56             else if(str[0] == 'A'){
    57                 int u, v;
    58                 scanf("%d%d", &u, &v);
    59                 add(u, v);
    60             }
    61             else if(str[0] == 'S'){
    62                 int u, v;
    63                 scanf("%d%d", &u, &v);
    64                 sub(u, v);
    65             }
    66         }
    67     }
    68     
    69     return 0;
    70 }
    View Code

      线段树和树状数组两者的复杂度同级,但是树状数组的常数明显优于线段树,编程复杂度也远远小于线段树。

      线段树的适用范围大于树状数组,凡是可以使用树状数组解决的问题,使用线段树一定可以解决。树状数组的优点是编程非常简洁,使用lowbit() 可以在很短的几步操作中完成核心操作,代码效率远远高于线段树。

  • 相关阅读:
    基于Token的WEB后台认证机制
    MySQL主键设计
    .NET学习笔记 -- 那堆名词到底是啥(CLR、CLI、CTS、CLS、IL、JIT)
    C#学习笔记 -- Attribute
    Log4Net指南
    Windows进程通信 -- 共享内存(1)
    windows进程通信 -- WM_COPYDATA消息
    C语言再学习之内存对齐
    找工作中……
    做了一个vue的同步tree 的npm组件包
  • 原文地址:https://www.cnblogs.com/0424lrn/p/12388660.html
Copyright © 2011-2022 走看看