浅谈线段树
浅谈线段树
要想检验一个程序员的数据结构能力,看看他的线段树水平就可以了。如果要给所有数据结构排名,线段树毫无疑问就是老大,“数据结构”是辅助线,线段树称得上是最优美的那条。线段树的应用太广了,可延展性太强了,它集合了运行速度高、应用方式多、编码相对简洁等优点,每一次发展都彰显着人类智慧。
如果把“分块”比作国家中划分省、市、县的管理方式,“线段树”就可比国家划南北,南北再划东西式的结构,它体现出分治思想,再加以tag技术,这是线段树实现的一个核心逻辑,以P3372 【模板】线段树 1为例,它的代码大概是这样的。
void buildtree(int k1,int l,int r){
if(l==r){
sum[k1]=a[l];
return ;
}
int mid=l+r>>1;
buildtree(k1*2,l,mid);
buildtree(k1*2+1,mid+1,r);
change(k1);
}void pushdown(int k1,int l,int mid,int r){
sum[k1*2]+=(mid-l+1)*tag[k1]; //其实这里可以写的结构化一些,写一个函数tg(k1,x)实现标记对节点的作用
sum[k1*2+1]+=(r-mid)*tag[k1];
tag[k1*2+1]+=tag[k1];
tag[k1*2]+=tag[k1];
tag[k1]=0;
}long long find(int k1,int l,int r,int L,int R){
if(l>R || r<L) return 0;
if(l>=L && r<=R) return sum[k1];
int mid=l+r>>1;
pushdown(k1,l,mid,r);
return find(k1*2,l,mid,L,R)+find(k1*2+1,mid+1,r,L,R);
}void addall(int k1,int l,int r,int L,int R,int gap){
if(l>R || r<L) return ;
if(l>=L && r<=R){
sum[k1]+=gap*(r-l+1);
tag[k1]+=gap;
return ;
}
int mid=l+r>>1;
pushdown(k1,l,mid,r);
addall(k1*2,l,mid,L,R,gap);
addall(k1*2+1,mid+1,r,L,R,gap);
change(k1);
}
相信大家对这种写法并不陌生,但会写这道板子,远远不够。我们需要对线段树有个更本质的认识。
线段树是什么?
笔者认为可以从两个角度认识,结构与功能[ 这似乎与生物学观点“结构与功能相适应”不谋而合]。
从功能上看,这一类线段树实质维护了两个东西。 info[ Info:即信息(information)] 与 tag[ 即懒标记(延迟更新技术)] 。我们不妨以P3373 【模板】线段树 2为例,这题可能让许多初学者感到头疼。事实上,我们只需要想明白,信息是什么,标记是什么,如何合并,可否合并,标记如何作用
于信息,标记如何合并。即 info=? , tag=? info+info=? tag+tag=? info+tag=? 剩下就是套模板了。
struct info {
int len, s;
} I, sum[4 * N];
//这里信息是区间和,为了更好的维护这一信息,我们引入了长度这一辅助信息
struct Tag {
int mul = 1, add;
} E, tag[4 * N];
//标记如题述,加法与乘法,我们规定操作时先乘后加
//”元“(I,E)的引入一定程度上可以简化代码
info operator+(info x, info y) {
info r;
r.len = x.len + y.len;
r.s = (x.s + y.s) % p;
return r;
}
Tag operator+(Tag x, Tag y) {
Tag r;
r.mul = (x.mul * y.mul) % p;
r.add = (x.add * y.mul + y.add) % p;
return r;
}
info operator+(info x, Tag y) {
info r;
r.len = x.len;
r.s = (x.len * y.add + x.s * y.mul) % p;
return r;
}
//三个操作的维护,代码应该还是很清楚的
这里另给一道题P6373 「StOI-1」IOI计数。该题只需维护信息的合并即可,很多时候,线段树的这三种操作时是可以省略几个的,这就产生了所谓标记永久化等的概念。
这种解释实际反映了信息学底层的逻辑。值得一提的是,这种解释进一步发展,引入一些矩阵的理论元素,就可以获得大名鼎鼎的“吉司机线段树”[ 详见吉老师在 2016 年国家集训队论文 中提到的线段树处理历史区间最值的问题。本文篇幅有限,在此不做展开。]。洛谷线段树的3道模板题,很好地反映了对线段树功能角度认识的不断加深。
另一个角度是从结构上说的。线段树是一个不断向下划分的二叉树结构(当然,它也可以是k叉的),我们注意到,线段树这个东西,它的空间开销是很大的,但事实上它有很多开销是浪费的。由此我们想出一个方法:动态开点线段树。
动态开点线段树在说这样一件事:我凭什么一开始就要开这么大空间,我需要时再开不行吗?于是,它在修改时,用到一个没用过的点就开一个,实现了空间的节约,这常用作值域线段树,让1e9的线段树不再需要借助离散化。
现在来看,线段树似乎没有想象的那么大,那么好,既然没有那么大,何不合并一下呢? 于是就有了线段树合并[ 可参考OIwiki对线段树合并分裂的介绍,十分详实https://oiwiki.org/ds/seg-merge-split/] ,进一步地,我们给线段树做个前缀和,用前缀和相减,得到“一段线段树”(这可能有些超出想象,但仔细想想,空间是可以重复利用的,线段树插入节点时形态其实并没有太大变化,所以复杂度没有问题,正确性也是显然的), 我们就有了——主席树[ 主席树是可持久化线段树的一种,由黄嘉泰发明,因名字缩写而被称为主席树]。
这便是从结构上对线段树的认识。
认识有了,那下一步自然是对其应用展开研究。
线段树的应用范围极广,几乎涵盖所有需要高效区间操作的场景。以下是几个典型的应用方向,结合经典例题帮助理解如何灵活运用线段树。
(一)二维数点与扫描线问题
二维数点问题(如统计矩形内的点数、计算矩形面积并等)是信息学竞赛中的经典问题,这类问题的核心难点在于处理二维区间的查询与更新。扫描线思想是解决这类问题的有效方法:通过 “离线一个维度,在线处理另一个维度”,将二维问题转化为一维问题,再用线段树高效求解。
经典例题:
P1502 窗口的星星:在平面直角坐标系中,给定多个星星的坐标和亮度,求一个固定大小的矩形窗口内能容纳的最大亮度和。
解题思路:将星星按 x 坐标排序(离线 x 维度),用扫描线从左到右移动,动态添加 / 删除星星(y 坐标对应的区间),并通过线段树维护 y 轴方向的区间最大亮度和(在线处理 y 维度)。
P5490 【模板】扫描线 & 矩形面积并:给定多个矩形,求它们的面积并。
解题思路:将矩形的上下边分别视为 “入边” 和 “出边”,按 y 坐标排序(离线 y 维度),用扫描线从下到上移动,动态更新 x 轴方向的区间覆盖长度(在线处理 x 维度),线段树用于维护 x 轴上被覆盖的总长度,每次移动扫描线时,用覆盖长度乘以 y 轴方向的移动距离,累加得到总面积。
(二)线段树优化动态规划(DP)
动态规划(DP)是解决多阶段决策问题的有效方法,但很多 DP 方程的转移过程时间复杂度较高(如 O (n^2)),无法直接通过大数据测试。线段树可以通过维护 DP 转移所需的区间信息,将转移时间复杂度优化为 O (log n),从而提高整体效率。
核心思想:
对于 DP 方程 dp[i] = max/min/sum{ dp[j] + w(j,i) }(j 属于某个区间),线段树可以维护区间内 dp[j] + w(j,i) 的最大值、最小值或和,从而快速查询 j 对应的最优值,完成 dp [i] 的计算。
典型例题:
最长上升子序列(LIS)的优化[ 这个例子些许不太合适,用线段树来优化有点大炮打蚊子了,树状数组就很好。选此例旨在用简单的问题反映线段树的应用]:传统 LIS 算法的时间复杂度为 O (n^2),通过线段树维护区间最大值,可以将时间复杂度优化为 O (n log n)。
解题思路:将元素值离散化后,对于每个元素 a [i],查询线段树中 [1, a [i]-1] 区间的最大 DP 值,dp [i] = 该最大值 + 1,再将 dp [i] 更新到线段树中 a [i] 对应的位置。
(三)树链剖分:树上问题的线段树求解
在树上进行路径查询、路径更新、子树查询等操作时,直接处理较为复杂。树链剖分的核心思想是将树映射为一个线性序列(通过重链剖分),将树上的路径操作转化为序列上的区间操作,再用线段树高效求解。
核心步骤:
重链剖分:将树划分为若干条不重叠的重链(重链是指包含节点数最多的路径),每个节点属于且仅属于一条重链;
序列映射:按重链的顺序对树节点进行编号,使每条重链对应的节点编号是连续的,从而将树上的路径转化为序列上的若干个连续区间;
线段树处理:用线段树维护该序列,树上的路径查询 / 更新操作转化为序列上的区间查询 / 更新操作,子树查询 / 更新操作则对应序列上的一个连续区间(因为子树的节点编号是连续的)。
典型例题:
树上路径求和与更新:给定一棵树,支持路径上所有节点值加 k,查询路径上所有节点值的和。
解题思路:通过树链剖分将路径转化为多个连续区间,用线段树维护区间和与加法延迟标记,完成路径更新和查询操作。
线段树作为数据结构中的 “全能选手”,其核心价值在于将分治思想与延迟标记技术完美结合,实现了高效的区间操作。从基础的区间求和、更新,到复杂的动态规划优化、树上问题求解,线段树的应用场景不断拓展,其拓展形式(动态开点、线段树合并、主席树等)也在不断丰富。线段树有关的知识还有很多,像树套树,k-D tree 等,一篇实在文章难以覆盖。
写在最后。笔者是一名尚不成熟的高一OIer,写这篇文章是为了总结一下对线段树的学习,其间必然有些问题及疏漏,希望批评指正,也希望多年后再读这篇文章得以感叹:这篇笔记还是差点火候。[2026.1.24记
]

浙公网安备 33010602011771号