线段树模板 & 树状数组模板

线段树的原理:将[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() 可以在很短的几步操作中完成核心操作,代码效率远远高于线段树。

原文地址:https://www.cnblogs.com/0424lrn/p/12388660.html