线段树简介
什么是线段树
线段树是一种数据结构,通常用于处理区间范围内的查询和更新问题。它的主要优点是能够以对数时间复杂度处理区间查询与区间更新。我们通常使用结构体(或数组)来模拟线段树的数据结构。
线段树能干什么
线段树主要有两个基本操作:
- 区间查询(例如:区间求和、区间最值、区间最大公约数等): 时间复杂度为
O(log n)。 - 区间更新(例如:区间加法、区间赋值等): 时间复杂度为
O(log n)。
线段树比树状数组更强大,因为它支持区间更新,而树状数组仅支持单点更新或单点查询。
洛谷 P3372【线段树 1】例题讲解
题目描述:
-
给定一个长度为
n的数组a[],支持两种操作:- 区间加法:对区间
[l, r]中的每个元素增加一个常数k。 - 区间求和:查询区间
[l, r]中所有元素的和。
- 区间加法:对区间
线段树解决方案
我们可以使用线段树来解决此问题,其中:
- 每个节点存储一个区间的和(例如:区间
[l, r]的和)。 - 每个节点还存储一个懒标记(
add),用于记录该区间需要被加上的常数值。
关键操作
-
懒标记(Lazy Propagation):为了优化区间更新操作,使用懒标记推迟更新。每次更新操作并不会立刻更新整棵树,而是标记某些节点需要更新,等到需要查询或更新时,再通过懒更新的方式进行推送。
-
区间更新与查询:
- 区间加法:当操作范围完全包含当前节点时,将该区间的每个元素加上
k,并将其标记为需要更新的节点。 - 区间查询:当查询某区间时,使用懒标记推迟的更新操作,确保查询的是最新的值。
- 区间加法:当操作范围完全包含当前节点时,将该区间的每个元素加上
代码实现
{% tabs code %}
以下是解决该问题的线段树代码,采用了懒标记来实现区间更新:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int a[N];
struct Node {
int l, r;
long long sum;
long long add;
} tree[N << 2];
// 向上更新节点
void pushup(int pos) {
tree[pos].sum = tree[pos << 1].sum + tree[pos << 1 | 1].sum;
}
// 向下传播懒标记
void pushdown(int pos) {
if (tree[pos].add) {
int ll = pos << 1, rr = pos << 1 | 1;
tree[ll].sum += tree[pos].add * (tree[ll].r - tree[ll].l + 1);
tree[ll].add += tree[pos].add;
tree[rr].sum += tree[pos].add * (tree[rr].r - tree[rr].l + 1);
tree[rr].add += tree[pos].add;
tree[pos].add = 0;
}
}
// 建树函数
void build(int pos, int l, int r) {
tree[pos].l = l;
tree[pos].r = r;
tree[pos].add = 0;
if (l == r) {
tree[pos].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build(pos << 1, l, mid);
build(pos << 1 | 1, mid + 1, r);
pushup(pos);
}
// 区间加法
void addd(int pos, int l, int r, int k) {
if (l <= tree[pos].l && tree[pos].r <= r) {
tree[pos].sum += (long long)k * (tree[pos].r - tree[pos].l + 1);
tree[pos].add += k;
return;
}
pushdown(pos);
int mid = (tree[pos].l + tree[pos].r) >> 1;
if (l <= mid) addd(pos << 1, l, r, k);
if (r > mid) addd(pos << 1 | 1, l, r, k);
pushup(pos);
}
// 区间求和
long long sum(int pos, int l, int r) {
if (l <= tree[pos].l && tree[pos].r <= r) {
return tree[pos].sum;
}
pushdown(pos);
int mid = (tree[pos].l + tree[pos].r) >> 1;
long long s = 0;
if (l <= mid) s += sum(pos << 1, l, r);
if (r > mid) s += sum(pos << 1 | 1, l, r);
return s;
}
signed main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
build(1, 1, n);
while (m--) {
int x;
cin >> x;
if (x == 1) {
int lll, rrr, k;
cin >> lll >> rrr >> k;
addd(1, lll, rrr, k);
} else if (x == 2) {
int lll, rrr;
cin >> lll >> rrr;
cout << sum(1, lll, rrr) << endl;
}
}
return 0;
}
代码讲解:
- 建树函数
build:通过递归将数组分段,并构建线段树。 - 区间加法函数
addd:在区间[l, r]上加上一个常数k,并进行懒标记。 - 区间求和函数
sum:查询区间[l, r]的和,并通过懒标记推迟更新。
操作解析:
- 区间加法:对于区间
[l, r],如果当前节点完全覆盖该区间,就直接在该节点上加上常数k,并标记为懒更新。否则,向下传播,更新子树。 - 区间求和:对于查询区间
[l, r],如果当前节点完全在查询范围内,直接返回该节点的值;否则,向下传播懒标记,查询左右子树的结果。
{% endtabs %}
总结
线段树通过懒标记优化了区间更新操作,使得在处理大量区间更新和查询时依然能保持高效性。通过这个例题,我们可以看到线段树不仅能快速处理区间求和,还能灵活应对动态区间更新的需求。
原创
线段树
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
赞赏支持
如果觉得文章对你有帮助,可以请作者喝杯咖啡 ☕
评论交流
欢迎留下你的想法