🎆🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎆
今天我要开启一个新计划----【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;
}