线段树
-
线段树可以在 \(𝑂(log𝑁)\) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
-
是常用的维护区间信息的数据结构。

建树
- 线段树将每个长度不为 1
的区间划分成左右两个区间递归求解,把整个线段划分为一个树形结构,通过合并左右两区间信息来求得该区间的信息。
// 从int a[N]数组中构建当前节点编号为p,管辖区间为[l,r]的节点
void _build(int s, int t, int p, ll a[]) {
// 当区间的左右端点重合时,直接更新该点即可
if (s == t) {
d[p] = a[s];
return;
}
// 将区间分为两个部分[s,mid],[mid+1,t],分别更新后更新[s,t]区间
int m = ((t - s) >> 1) + s;
// 区间[s,mid]对应节点为2*p
_build(s, m, 2 * p, a);
// 区间[mid+1,t]对应节点为2*p+1
_build(m + 1, t, 2 * p + 1, a);
// 最后更新[s,t]区间
d[p] = d[2 * p] + d[2 * p + 1];
}
区间更新
- 当要求更新区间\([l,r]\)中的所有数时,一个个遍历更新是十分低效的。
- 引入一个懒惰标记,通过延迟对节点信息的更改,从而减少可能不必要的操作次数。
- 更新一个节点时,通过打标记的方法表明该节点对应的区间在某一次操作中被更改,但不更新该节点的子节点的信息。
- 实质性的修改则在下一次访问带有标记的节点时才进行。
// 封装下推懒标记的函数,将管辖区间为[s,t]的节点p的懒标记下推
void push(int s, int t, int p) {
// 将区间划分为[s,m],[m+1,t]
int m = ((t - s) >> 1) + s;
// 如果存在懒标记,且不为叶子节点则下推懒标记
if (lazy[p] && s != t) {
// 更新两个孩子节点
d[2 * p] += (m - s + 1) * lazy[p];
d[2 * p + 1] += (t - m) * lazy[p];
// 更新两个孩子节点的懒标记
lazy[2 * p] += lazy[p];
lazy[2 * p + 1] += lazy[p];
// 清空当前节点懒标记
lazy[p] = 0;
}
}
// 将区间[l,r]的数都加上c; p是当前节点编号,管辖区间[s,t]
void _update(int l, int r, ll c, int s, int t, int p) {
// 如果[s,t]是[l,r]的子集,则直接更新并带上懒标记。
if (l <= s && t <= r) {
d[p] += (t - s + 1) * c;
lazy[p] += c;
return;
}
// 否则考虑两个子区间[s,m],[m+1,t]
int m = ((t - s) >> 1) + s;
// 访问了节点p,下推懒标记,更新子节点
push(s, t, p);
// 如果[l,r]与[s,m]有交集,更新对应子节点
if (l <= m) {
_update(l, r, c, s, m, 2 * p);
}
// 如果[l,r]与[m+1,t]有交集,更新对应子节点
if (r > m) {
_update(l, r, c, m + 1, t, 2 * p + 1);
}
// 使用更新后的子节点更新自身。
d[p] = d[2 * p] + d[2 * p + 1];
}
区间查询
// 查询区间为[l,r]的区间和;当前节点编号为p,管辖区间[s,t]
ll _query(int l, int r, int s, int t, int p) {
// 如果[s,t]是[l,r]的子集,直接返回结果
if (l <= s && t <= r) {
return d[p];
}
// 否则考虑[s,m],[m+1,t]两个区间。
int m = ((t - s) >> 1) + s;
// 访问了节点p,下推懒标记
push(s, t, p);
// 记录答案
ll ans = 0;
// 如果[s,m]与[l,r]有交集,向下访问子节点2*p
if (l <= m) {
ans = _query(l, r, s, m, 2 * p);
}
// 如果[m+1,t]与[l,r]有交集,向下访问子节点2*p+1
if (r > m) {
ans += _query(l, r, m + 1, t, 2 * p + 1);
}
//返回答案
return ans;
}
封装结构(区间和,区间加)
struct segTree {
ll d[4 * N];
ll lazy[4 * N];
int n;
void _build(int s, int t, int p, ll a[]) {
if (s == t) {
d[p] = a[s];
return;
}
int m = ((t - s) >> 1) + s;
_build(s, m, 2 * p, a);
_build(m + 1, t, 2 * p + 1, a);
d[p] = d[2 * p] + d[2 * p + 1];
}
void push(int s, int t, int p) {
int m = ((t - s) >> 1) + s;
if (lazy[p] && s != t) {
d[2 * p] += (m - s + 1) * lazy[p];
d[2 * p + 1] += (t - m) * lazy[p];
lazy[2 * p] += lazy[p];
lazy[2 * p + 1] += lazy[p];
lazy[p] = 0;
}
}
void _update(int l, int r, ll c, int s, int t, int p) {
if (l <= s && t <= r) {
d[p] += (t - s + 1) * c;
lazy[p] += c;
return;
}
int m = ((t - s) >> 1) + s;
push(s, t, p);
if (l <= m) {
_update(l, r, c, s, m, 2 * p);
}
if (r > m) {
_update(l, r, c, m + 1, t, 2 * p + 1);
}
d[p] = d[2 * p] + d[2 * p + 1];
}
ll _query(int l, int r, int s, int t, int p) {
if (l <= s && t <= r) {
return d[p];
}
int m = ((t - s) >> 1) + s;
push(s, t, p);
ll ans = 0;
if (l <= m) {
ans = _query(l, r, s, m, 2 * p);
}
if (r > m) {
ans += _query(l, r, m + 1, t, 2 * p + 1);
}
return ans;
}
void build(int n, ll a[]) {
this->n = n;
memset(lazy, 0, sizeof(lazy));
_build(1, n, 1, a);
}
void update(int l, int r, int c) {
_update(l, r, c, 1, n, 1);
}
ll query(int l, int r) {
return _query(l, r, 1, n, 1);
}
} tree;
例题
- P3372 【模板】线段树 1 - 洛谷
- 标准的线段树模板题
#include <bits/stdc++.h>
#define ll long long
#define int long long
#define IOS \
ios::sync_with_stdio(false); \
cin.tie(nullptr);
#define endl '\n'
int const inf = 0x3f3f3f3f;
ll const infll = 0x3f3f'3f3f'3f3f'3f3f;
double const PI = acos(-1.0);
using namespace std;
// ifstream fin("input.txt");
// ofstream fout("output.txt");
// #define cin fin
// #define cout fout
int const N = 1e5 + 10;
int n, m;
ll a[N];
struct segTree {
ll d[4 * N];
ll lazy[4 * N];
int n;
void _build(int s, int t, int p, ll a[]) {
if (s == t) {
d[p] = a[s];
return;
}
int m = ((t - s) >> 1) + s;
_build(s, m, 2 * p, a);
_build(m + 1, t, 2 * p + 1, a);
d[p] = d[2 * p] + d[2 * p + 1];
}
void push(int s, int t, int p) {
int m = ((t - s) >> 1) + s;
if (lazy[p] && s != t) {
d[2 * p] += (m - s + 1) * lazy[p];
d[2 * p + 1] += (t - m) * lazy[p];
lazy[2 * p] += lazy[p];
lazy[2 * p + 1] += lazy[p];
lazy[p] = 0;
}
}
void _update(int l, int r, ll c, int s, int t, int p) {
if (l <= s && t <= r) {
d[p] += (t - s + 1) * c;
lazy[p] += c;
return;
}
int m = ((t - s) >> 1) + s;
push(s, t, p);
if (l <= m) {
_update(l, r, c, s, m, 2 * p);
}
if (r > m) {
_update(l, r, c, m + 1, t, 2 * p + 1);
}
d[p] = d[2 * p] + d[2 * p + 1];
}
ll _query(int l, int r, int s, int t, int p) {
if (l <= s && t <= r) {
return d[p];
}
int m = ((t - s) >> 1) + s;
push(s, t, p);
ll ans = 0;
if (l <= m) {
ans = _query(l, r, s, m, 2 * p);
}
if (r > m) {
ans += _query(l, r, m + 1, t, 2 * p + 1);
}
return ans;
}
void build(int n, ll a[]) {
this->n = n;
memset(lazy, 0, sizeof(lazy));
_build(1, n, 1, a);
}
void update(int l, int r, int c) {
_update(l, r, c, 1, n, 1);
}
ll query(int l, int r) {
return _query(l, r, 1, n, 1);
}
} tree;
signed main() {
IOS cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
tree.build(n, a);
while (m--) {
int op;
cin >> op;
if (op == 1) {
int x, y;
ll k;
cin >> x >> y >> k;
tree.update(x, y, k);
} else {
int x, y;
cin >> x >> y;
cout << tree.query(x, y) << endl;
}
}
// fin.close(),fout.close();
return 0;
}
- P3373 【模板】线段树 2 - 洛谷
- 使用两个懒标记数组支持加法与乘法,更新乘法懒标记时需要更新加法懒标记
#include <bits/stdc++.h>
#define ll long long
// #define int long long
#define IOS \
ios::sync_with_stdio(false); \
cin.tie(nullptr);
#define endl '\n'
int const inf = 0x3f3f3f3f;
ll const infll = 0x3f3f'3f3f'3f3f'3f3f;
double const PI = acos(-1.0);
using namespace std;
// ifstream fin("input.txt");
// ofstream fout("output.txt");
// #define cin fin
// #define cout fout
int const N = 1e5 + 10;
int n, q, m;
int a[N];
inline int add(int x, int y) {
return (x + y * 1LL) % m;
}
inline int mul(int x, int y) {
return (1LL * x * y ) % m;
}
struct segTree {
int d[4 * N];
// 使用多个懒标记进行多种运算
int lazy[4 * N]; // 加法懒标记
int lzmul[4 * N]; // 乘法懒标记
int n;
void _build(int s, int t, int p, int a[]) {
lzmul[p] = 1;
if (s == t) {
d[p] = a[s];
return;
}
int mid = ((t - s) >> 1) + s;
_build(s, mid, 2 * p, a);
_build(mid + 1, t, 2 * p + 1, a);
d[p] = add(d[2 * p], d[2 * p + 1]);
}
// 下推懒惰标记
void pushdown(int s, int t, int p) {
int mid = ((t - s) >> 1) + s;
if (lzmul[p] != 1 && s != t) {
d[2 * p] = mul(d[2 * p], lzmul[p]);
d[2 * p + 1] = mul(d[2 * p + 1], lzmul[p]);
lzmul[2 * p] = mul(lzmul[2 * p], lzmul[p]);
lzmul[2 * p + 1] = mul(lzmul[2 * p + 1], lzmul[p]);
// 懒标记传递
lazy[2 * p] = mul(lazy[2 * p], lzmul[p]);
lazy[2 * p + 1] = mul(lazy[2 * p + 1], lzmul[p]);
lzmul[p] = 1;
}
if (lazy[p] && s != t) {
d[2 * p] = add(d[2 * p], mul((mid - s + 1), lazy[p]));
d[2 * p + 1] = add(d[2 * p + 1], mul((t - mid), lazy[p]));
lazy[2 * p] = add(lazy[2 * p], lazy[p]);
lazy[2 * p + 1] = add(lazy[2 * p + 1], lazy[p]);
lazy[p] = 0;
}
}
void jia(int l, int r, int c, int s, int t, int p) {
if (l <= s && t <= r) {
d[p] = add(d[p], mul(c, (t - s + 1)));
lazy[p] = add(lazy[p], c);
return;
}
int mid = ((t - s) >> 1) + s;
pushdown(s, t, p);
if (l <= mid) {
jia(l, r, c, s, mid, 2 * p);
}
if (r >= mid + 1) {
jia(l, r, c, mid + 1, t, 2 * p + 1);
}
d[p] = add(d[2 * p], d[2 * p + 1]);
}
void chen(int l, int r, int c, int s, int t, int p) {
if (l <= s && t <= r) {
d[p] = mul(d[p], c);
lzmul[p] = mul(lzmul[p], c);
// 懒标记传递
lazy[p] = mul(lazy[p], c);
return;
}
int mid = ((t - s) >> 1) + s;
pushdown(s, t, p);
if (l <= mid) {
chen(l, r, c, s, mid, 2 * p);
}
if (r >= mid + 1) {
chen(l, r, c, mid + 1, t, 2 * p + 1);
}
d[p] = add(d[2 * p], d[2 * p + 1]);
}
int query(int l, int r, int s, int t, int p) {
if (l <= s && t <= r) {
return d[p];
}
int mid = ((t - s) >> 1) + s;
pushdown(s, t, p);
int ans = 0;
if (l <= mid) {
ans = add(ans, query(l, r, s, mid, 2 * p));
}
if (r >= mid + 1) {
ans = add(ans, query(l, r, mid + 1, t, 2 * p + 1));
}
return ans;
}
} tree;
signed main() {
IOS cin >> n >> q >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
tree.n = n;
tree._build(1, n, 1, a);
while (q--) {
int op;
cin >> op;
if (op == 1) {
int x, y, k;
cin >> x >> y >> k;
tree.chen(x, y, k, 1, n, 1);
} else if (op == 2) {
int x, y, k;
cin >> x >> y >> k;
tree.jia(x, y, k, 1, n, 1);
} else {
int x, y;
cin >> x >> y;
cout << tree.query(x, y, 1, n, 1) << endl;
}
}
// fin.close(),fout.close();
return 0;
}
动态开点线段树
- 堆式储存的情况下,需要给线段树开 \(4𝑛\) 大小的数组。为了节省空间,我们可以不一次性建好树,而是在最初只建立一个根结点代表整个区间。当我们需要访问某个子区间时,才建立代表这个区间的子结点。
- 动态开点线段树的核心思想就是:结点只有在有需要的时候才被创建。
- 见 线段树基础 - OI Wiki

浙公网安备 33010602011771号