线段树与懒标记
1040 字
5 分钟
线段树与懒标记

线段树与懒标记
线段树适合处理区间问题,比如:
- 区间求和
- 区间最大值
- 单点修改
- 区间修改
当题目既有区间查询,又有区间修改时,通常需要懒标记。
1、线段树解决什么问题
假设有数组:
1 2 3 4 5需要频繁执行:
查询 [l, r] 的和把 [l, r] 中每个数都加上 c如果每次都暴力遍历区间,复杂度太高。线段树可以把这些操作做到 O(log n)。
2、线段树节点存什么
常见节点结构:
struct Node { int l, r; long long sum; long long add;} tr[N * 4];含义:
l, r:当前节点表示的区间sum:当前区间的和add:懒标记,表示这个区间整体还欠着多少增量没有下传3、建树
建树就是递归划分区间。
void build(int u, int l, int r) { tr[u] = {l, r, a[l], 0};
if (l == r) return;
int mid = (l + r) >> 1; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); pushup(u);}u 是当前节点编号,左儿子是 u << 1,右儿子是 u << 1 | 1。
4、pushup:用子节点更新父节点
void pushup(int u) { tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;}父节点的区间和等于左右子区间的和。
5、懒标记是什么
如果某次修改完全覆盖当前区间,就不需要继续递归到叶子节点。
比如要让 [1, 100] 都加 5,当前节点刚好表示 [1, 100],直接更新:
sum += 区间长度 * 5add += 5这个 add 表示:以后如果需要访问子节点,再把这个修改传下去。
6、pushdown:下传懒标记
void pushdown(int u) { auto& root = tr[u]; auto& left = tr[u << 1]; auto& right = tr[u << 1 | 1];
if (root.add) { left.add += root.add; left.sum += (long long)(left.r - left.l + 1) * root.add;
right.add += root.add; right.sum += (long long)(right.r - right.l + 1) * root.add;
root.add = 0; }}只有当要继续往下访问子节点时,才需要 pushdown。
7、完整模板:区间加,区间求和
#include <bits/stdc++.h>using namespace std;
const int N = 100010;
int n, m;long long a[N];
struct Node { int l, r; long long sum; long long add;} tr[N * 4];
void pushup(int u) { tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;}
void build(int u, int l, int r) { tr[u] = {l, r, a[l], 0};
if (l == r) return;
int mid = (l + r) >> 1; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); pushup(u);}
void pushdown(int u) { auto& root = tr[u]; auto& left = tr[u << 1]; auto& right = tr[u << 1 | 1];
if (root.add) { left.add += root.add; left.sum += (long long)(left.r - left.l + 1) * root.add;
right.add += root.add; right.sum += (long long)(right.r - right.l + 1) * root.add;
root.add = 0; }}
void modify(int u, int l, int r, int d) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].sum += (long long)(tr[u].r - tr[u].l + 1) * d; tr[u].add += d; return; }
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1; if (l <= mid) modify(u << 1, l, r, d); if (r > mid) modify(u << 1 | 1, l, r, d);
pushup(u);}
long long query(int u, int l, int r) { if (tr[u].l >= l && tr[u].r <= r) { return tr[u].sum; }
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1; long long res = 0;
if (l <= mid) res += query(u << 1, l, r); if (r > mid) res += query(u << 1 | 1, l, r);
return res;}
int main() { cin >> n >> m; for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
while (m--) { char op; int l, r, d; cin >> op >> l >> r;
if (op == 'Q') { cout << query(1, l, r) << endl; } else { cin >> d; modify(1, l, r, d); } }
return 0;}8、容易写错的地方
(1)数组要开 4 * N
线段树节点数量一般开:
tr[N * 4]不要只开 N。
(2)区间和建议用 long long
区间加和区间求和很容易超过 int。
(3)查询和修改前要下传懒标记
只要要访问子节点,就先:
pushdown(u);否则子节点保存的值可能不是最新的。
原图笔记




懒标记处理

总结图

总结
线段树可以记成:
build 建树pushup 子节点更新父节点pushdown 懒标记下传modify 区间修改query 区间查询懒标记的意义是:能不往下走就先不往下走,把修改暂存在当前节点,等真正需要访问子节点时再下传。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














