懒标记线段树模板详解

懒标记线段树模板详解

1. 简介

懒标记线段树(Lazy Segment Tree)是线段树的一种高级变体,它支持高效的区间查询和区间修改操作。主要特点:

  1. 支持区间修改和区间查询
  2. 时间复杂度均为 O(log n)
  3. 使用懒标记技术延迟更新
  4. 支持多种信息维护

2. 实现原理

2.1 基本结构

线段树将区间划分为多个子区间,每个节点维护一个区间的信息。本模板使用两个核心结构:

  1. Info:维护区间信息(如区间和、最大值、最小值等)
  2. Tag:维护懒标记信息(如区间加法标记)

2.2 核心操作

  1. 下推(Push):将当前节点的懒标记传递给子节点
  2. 上传(Pull):从子节点更新当前节点的信息
  3. 应用(Apply):将懒标记应用到当前节点

3. 模板代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
template<class Info, class Tag>
struct LazySegmentTree {
int n;
std::vector<Info> info;
std::vector<Tag> tag;
LazySegmentTree() : n(0) {}
LazySegmentTree(int n_, Info v_ = Info()) {
init(n_, v_);
}
template<class T>
LazySegmentTree(std::vector<T> init_) {
init(init_);
}
void init(int n_, Info v_ = Info()) {
init(std::vector(n_, v_));
}
template<class T>
void init(std::vector<T> init_) {
n = init_.size();
info.assign(4 << std::__lg(n), Info());
tag.assign(4 << std::__lg(n), Tag());
std::function<void(int, int, int)> build = [&](int p, int l, int r) {
if (r - l == 1) {
info[p] = init_[l];
return;
}
int m = (l + r) / 2;
build(2 * p, l, m);
build(2 * p + 1, m, r);
pull(p);
};
build(1, 0, n);
}
void pull(int p) {
info[p] = info[2 * p] + info[2 * p + 1];
}
void apply(int p, const Tag &v) {
info[p].apply(v);
tag[p].apply(v);
}
void push(int p) {
apply(2 * p, tag[p]);
apply(2 * p + 1, tag[p]);
tag[p] = Tag();
}
void modify(int p, int l, int r, int x, const Info &v) {
if (r - l == 1) {
info[p] = v;
return;
}
int m = (l + r) / 2;
push(p);
if (x < m) {
modify(2 * p, l, m, x, v);
} else {
modify(2 * p + 1, m, r, x, v);
}
pull(p);
}
void modify(int p, const Info &v) {
modify(1, 0, n, p, v);
}
Info rangeQuery(int p, int l, int r, int x, int y) {
if (l >= y || r <= x) {
return Info();
}
if (l >= x && r <= y) {
return info[p];
}
int m = (l + r) / 2;
push(p);
return rangeQuery(2 * p, l, m, x, y) + rangeQuery(2 * p + 1, m, r, x, y);
}
Info rangeQuery(int l, int r) {
return rangeQuery(1, 0, n, l, r);
}
void rangeApply(int p, int l, int r, int x, int y, const Tag &v) {
if (l >= y || r <= x) {
return;
}
if (l >= x && r <= y) {
apply(p, v);
return;
}
int m = (l + r) / 2;
push(p);
rangeApply(2 * p, l, m, x, y, v);
rangeApply(2 * p + 1, m, r, x, y, v);
pull(p);
}
void rangeApply(int l, int r, const Tag &v) {
return rangeApply(1, 0, n, l, r, v);
}
void half(int p, int l, int r) {
if (info[p].act == 0) {
return;
}
if ((info[p].min + 1) / 2 == (info[p].max + 1) / 2) {
apply(p, {-(info[p].min + 1) / 2});
return;
}
int m = (l + r) / 2;
push(p);
half(2 * p, l, m);
half(2 * p + 1, m, r);
pull(p);
}
void half() {
half(1, 0, n);
}

template<class F>
int findFirst(int p, int l, int r, int x, int y, F &&pred) {
if (l >= y || r <= x) {
return -1;
}
if (l >= x && r <= y && !pred(info[p])) {
return -1;
}
if (r - l == 1) {
return l;
}
int m = (l + r) / 2;
push(p);
int res = findFirst(2 * p, l, m, x, y, pred);
if (res == -1) {
res = findFirst(2 * p + 1, m, r, x, y, pred);
}
return res;
}
template<class F>
int findFirst(int l, int r, F &&pred) {
return findFirst(1, 0, n, l, r, pred);
}
template<class F>
int findLast(int p, int l, int r, int x, int y, F &&pred) {
if (l >= y || r <= x) {
return -1;
}
if (l >= x && r <= y && !pred(info[p])) {
return -1;
}
if (r - l == 1) {
return l;
}
int m = (l + r) / 2;
push(p);
int res = findLast(2 * p + 1, m, r, x, y, pred);
if (res == -1) {
res = findLast(2 * p, l, m, x, y, pred);
}
return res;
}
template<class F>
int findLast(int l, int r, F &&pred) {
return findLast(1, 0, n, l, r, pred);
}
};

constexpr i64 INF = 1E18;

struct Tag {
i64 add = 0;

void apply(const Tag &t) & {
add += t.add;
}
};

struct Info {
i64 min = INF;
i64 max = -INF;
i64 sum = 0;
i64 act = 1;

void apply(const Tag &t) & {
min += t.add;
max += t.add;
sum += act * t.add;
}
};

Info operator+(const Info &a, const Info &b) {
Info c;
c.min = std::min(a.min, b.min);
c.max = std::max(a.max, b.max);
c.sum = a.sum + b.sum;
c.act = a.act + b.act;
return c;
}

4. 核心功能说明

4.1 基础操作

1
2
3
void pull(int p)
void apply(int p, const Tag &v)
void push(int p)
  • pull:从子节点更新当前节点信息
  • apply:将标记应用到节点
  • push:将标记下推给子节点

4.2 区间操作

1
2
3
void modify(int p, const Info &v)
Info rangeQuery(int l, int r)
void rangeApply(int l, int r, const Tag &v)
  • modify:单点修改
  • rangeQuery:区间查询
  • rangeApply:区间修改

4.3 二分查找

1
2
3
4
template<class F>
int findFirst(int l, int r, F &&pred)
template<class F>
int findLast(int l, int r, F &&pred)
  • findFirst:查找第一个满足条件的位置
  • findLast:查找最后一个满足条件的位置

5. 时间复杂度分析

  • 初始化:O(n)
  • 单点修改:O(log n)
  • 区间查询:O(log n)
  • 区间修改:O(log n)
  • 二分查找:O(log n)

6. 应用场景

  1. 区间求和问题
  2. 区间最值问题
  3. 区间统计问题
  4. 动态维护区间信息
  5. 区间修改问题

7. 使用示例

7.1 区间加法和查询

1
2
3
4
5
6
7
8
// 创建线段树
LazySegmentTree<Info, Tag> seg(n);

// 区间加法
seg.rangeApply(l, r, {x}); // 区间 [l,r) 内所有数加 x

// 区间查询
Info result = seg.rangeQuery(l, r); // 查询区间 [l,r) 的信息

7.2 自定义信息维护

1
2
3
4
5
6
7
8
9
10
11
12
// 自定义 Info 结构体
struct CustomInfo {
// 自定义区间信息
void apply(const Tag &t) & {
// 定义如何应用标记
}
};

// 自定义合并操作
CustomInfo operator+(const CustomInfo &a, const CustomInfo &b) {
// 定义如何合并信息
}

8. 注意事项

  1. 区间范围是左闭右开 [l, r)
  2. 需要正确实现 Info 和 Tag 的结构体
  3. 懒标记的下推时机很重要
  4. 注意防止整数溢出
  5. 合并操作要满足结合律

9. 优化技巧

  1. 使用位运算优化节点编号计算
  2. 合理设计 Info 和 Tag 结构
  3. 适时清空懒标记
  4. 利用二分查找优化特定问题

10. 总结

懒标记线段树是一个强大的数据结构,它通过懒标记技术实现了高效的区间操作。本模板的设计非常灵活,通过模板参数可以适应各种不同的问题需求。掌握这个数据结构对于解决区间相关的问题非常有帮助。