【C++天梯计划】1.15 线段树(Segment Tree)

🎆🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎆
今天我要开启一个新计划----【C++天梯计划】
目的是通过天梯计划,通过题目和知识点串联的方式,完成C++复习与巩固。

什么是线段树?

线段树,也叫区间树,是一个完全二叉树,它在各个节点保存一条线段(即“子数组”),因而常用于解决数列维护问题,基本能保证每个操作的复杂度为O(lgN)。
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩。

线段树的定义

线段树顾名思义, 是用来存放给定区间内对应信息的一种数据结构。与树状数组相似,线段树也用来处理数组相应的区间查询和元素更新操作。与树状数组不同的是,线段树不止可以适用于区间求和的查询,也可以进行区间最大值,区间最小值或者区间异或值的查询。对应于树状数组,线段树进行更新的操作为O(logn),进行区间查询的操作也为O(logn)

例题1:区间修改与查询

题目描述

给定由N个整数构成的数列,再给定M条指令,每条指令可能是如下两种之一:
1.C l r d,表示将区间[l,r]之间的每个数都加上整数d;
2.Q l r,表示询问区间[l,r]之间所有数的和;
对于每次询问,请输出对应的和。

输入

第1行有2个整数N和M;
第2行有N个整数,空格隔开;
接下来M行,每行读入一条题目描述的指令。
1≤N,M≤105, -10000≤d≤10000,-109≤N个整数≤109。

输出

输出若干行,每行一个整数,代表了对于每次询问输出的结果。

样例

输入
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
输出
4
55
9
15

代码
#include <bits/stdc++.h>
using namespace std; 
const int maxn = 1e5 + 100; 
typedef long long LL; 

template <typename T>
inline void read(T &s) {
    s = 0; 
    T w = 1, ch = getchar(); 
    while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); }
    while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    s *= w; 
}

int n, m; 
int a[maxn];
LL c[2][maxn], sum[maxn]; 

inline int lowbit(int x) { return x & (-x); }

inline void add(int k, int x, int y) {
    for (; x <= n; x += lowbit(x)) 
        c[k][x] += y; 
}

inline LL ask(int k, int x) {
    LL ans = 0ll; 
    for (; x; x -= lowbit(x)) 
        ans += c[k][x]; 
    
    return ans; 
}

int main() {
    read(n), read(m); 
    for (int i = 1; i <= n; ++i) {
        read(a[i]); 
        sum[i] = sum[i - 1] + a[i]; 
    }
    
    while (m--) {
        char ch; cin >> ch;  
        
        if (ch == 'C') {
            int l, r, d; 
            read(l), read(r), read(d); 
            
            add(0, l, d); 
            add(0, r + 1, -d); 
            add(1, l, l * d); 
            add(1, r + 1, -(r + 1) * d); 
        }
        else if (ch == 'Q') {
            int l, r; 
            read(l), read(r); 
            
            LL ans = sum[r] + (r + 1) * ask(0, r) - ask(1, r); 
            // check; 
            ans -= sum[l - 1] + l * ask(0, l - 1) - ask(1, l - 1);  
            
            printf("%lld\n", ans); 
        }
    }
    return 0; 
}

例题2:数列修改求和

题目描述

给定一个长度为 nn 的数列,数列中默认初始值为 00 ,数列支持 22 种操作:
修改:将第 aa 个数加上值 bb ;
查询:查询数列中区间为 [a,b][a,b] (从第 aa 个数开始到第 bb 个数结束)的所有数的和。
读入 mm 次修改或者查询的指令,针对每次查询,请输出对应的区间

输入

输入数据第一行包含两个正整数 n,mn,m (n≤10……5,m≤5 \times 10^5n≤10……5,m≤5×10
5
),接下来有 mm 行。
每行有三个正整数 k,a,bk,a,b (k=0k=0 或 11,a,b≤na,b≤n),k=0k=0 时表示将 aa 处数字加上 bb ,k=1k=1 时表示询问区间 [a,b][a,b] 内所有数的和。

输出

对于每个询问输出对应的区间和。(本题读入的 nn 个数在 [-2{31},2{31}-1][−2
31
,2
31
−1] 的范围内,求出的区间和在 [-2{63},2{63}-1][−2
63
,2
63
−1] )

样例

输入
10 20
0 1 10
1 1 4
0 6 6
1 4 10
1 8 9
1 4 9
0 10 2
1 1 8
0 2 10
1 3 9
0 7 8
0 3 10
0 1 1
1 3 8
1 6 9
0 5 5
1 1 8
0 4 2
1 2 8
0 1 1
输出
10
6
0
6
16
6
24
14
50
41

代码
#include <bits/stdc++.h>
using namespace std;
long long bits[500005], n; 
int lowbit(int x) {
    return x & -x;
}
void Update(int i, long long k) {
    while (i <= n) {
        bits[i] += k;
        i += lowbit(i);
    }
}
long long PrefixSum(int i) {
    long long ans = 0;
    while (i > 0) {
        ans += bits[i];
        i -= lowbit(i);
    }
    return ans;
}
long long RangeSum(int left, int right) {
    return PrefixSum(right) - PrefixSum(left - 1);
}
int main() {
    int q, judge, delta, left, right;
    scanf("%lld%d", &n, &q);
    while (q--) {
        scanf("%d", &judge);
        if (judge ==1) {
            scanf("%d%d", &left, &right);
            printf("%lld\n", RangeSum(left, right));
        }
        else {
            scanf("%d%d", &judge, &delta);
            Update(judge, delta);
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值