线段树 Segment Tree
线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。可以在 \(O(\log n)\) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
以区间求和问题为例,对\(a={10,11,12,13,14}\)建立以下线段树:
很多教程上线段树都是用递归来实现的,这导致函数参数众多而且效率不高,代码并不容易记忆。实际上通过简单的位运算,线段树实现可以大大简化,不但效率高,而且更容易理解和记住。例如对于求和问题可以实现如下(来自CPH 9.3,注释是我加的):
void set(int k, int x) { // 更新元素k为x
k += n;
tree[k] = x;
for (k /= 2; k >= 1; k /= 2) {
tree[k] = tree[2*k]+tree[2*k+1];
}
}
int sum(int a, int b) { // 返回区间[a,b]的和
a += n; b += n;
int s = 0;
while (a <= b) {
if (a%2 == 1) s += tree[a++]; // 左边界如果是右子树,则合并进结果,并右移
if (b%2 == 0) s += tree[b--]; // 右边界如果是左子树,则合并进结果,然后左移
a /= 2; b /= 2;
}
return s;
}
这个代码对于任意n都是可以的。
AI.Cash的文章Efficient and easy segment trees讲得很清楚,包括高效实现(如上不用递归 ),区间修改、lazy propagation等。
更多问题比如区间修改、堆式建树,见OI-Wiki。
习题
- P3372 【模板】线段树 1 普及/提高-
- P3373 【模板】线段树 2 普及+/提高
- P1531 I Hate It 普及/提高-
- P4086 [USACO17DEC]My Cow Ate My Homework S 普及/提高-
- P1471 方差 提高+/省选-
- P3870 [TJOI2009] 开关 普及/提高-
- P1253 [yLOI2018] 扶苏的问题 普及+/提高
- P1438 无聊的数列 提高+/省选-
- P4513 小白逛公园 提高+/省选-
- P5522 [yLOI2019] 棠梨煎雪 提高+/省选-
- P2572 [SCOI2010] 序列操作 省选/NOI-
- HDU 1166 敌兵布阵 提高+/省选-
- POJ 3468 A Simple Problem with Integers
- HDU 1698 Just a Hook: 线段树+区间修改+懒惰标志
- POJ 2528 Major's Posters: 离散化+线段树
- HDU 4027 Can you answer these queries?
- HDU 3974 Assign the task: 线段树区间更新
- HDU 1540 Tunnel Warfare: 线段树区间合并