zkw 线段树
线段树
俗称 重口味 ,与 类似,咳咳...
一、线段树简介
线段树是由清华大学姚班大佬 张昆玮 所创立的一种线段树储存结构,由于其基于非递归的实现方式以及精简的代码和较高的效率而闻名。甚至,线段树能够可持久化。
我们从算法的角度对基础线段树进行分析:其实线段树算法本身的本质仍是统计。因此我们可以从统计的角度入手对线段树进行分析:线段树是将一个个数轴划分为区间进行处理的,因此我们面对的往往是一系列的离散量,这导致了我们在使用时的线段树单纯的退化为一棵"点树"(即最底层的线段树只包含一个点)。基于这一点可以入手对线段树进行优化。
二、线段树的构造原理
首先,我们忽略线段树中的数据,从线段树的框架结构入手进行分析:如图所示是一颗采用堆式储存的基本线段树:

我们将节点编号转换为二进制:

观察转为二进制后的结点规律:在基础线段树的学习中,我们知道对于任意结点,其左子节点为,右子节点为。这个规律是我们从根结点出发向叶节点寻找的规律。那么现在我们换个思路:从叶结点出发向根结点寻找规律:
- 当前结点的父节点一定是当前的结点右移一位(舍弃低位)得到的
- 当前结点的左子节点为,右子节点为
- 每一层结点按照顺序排列,第层有个节点
- 最后一层的结点个数 = 值域
因为最后一层的结点个数=值域,假设给定数组,含有元素。
我们约定,无论元素的个数是否达到,最后一层的空间都开到,无数据的叶节点空置即可。
三、线段树基本操作
1.建树操作
void build(int n){
for(m = 1; m <= n;) m <<= 1;
for (int i = m + 1; i <= m + n; ++i) op_array[i] = read();
for (int i = m - 1; i; --i) operation(),
}
- 如果维护区间和,那么
sum[i] = sum[i << 1] + sum[i << 1 | 1];
- 如果维护区间最小值,那么
minn[i] = min(minn[i << 1], minn[i << 1 | 1]); //不支持修改操作
minn[i] = min(minn[i << 1], minn[i << 1 | 1]),
minn[i << 1] -= minn[i], minn[i << 1 | 1] -= minn[i];
- 如果维护区间最大值,那么
maxx[i] = max(maxx[i << 1], maxx[i << 1 | 1]); //不支持修改操作
maxx[i] = max(maxx[i << 1], maxx[i << 1 | 1]),
maxx[i << 1] -= maxx[i], maxx[i << 1 | 1] -= maxx[i];
2.单点查询
这个操作是相对容易理解的,就是一个从叶子结点开始,不断向父节点走,同时累加沿路的权值的过程。
int query(int x){
int ans = 0;
for (x += m; x; x >>= 1) ans += minn[s];
return ans;
}
3.单点修改
单点修改的思路非常简单,只需要修改当前结点并更新父节点即可。
void update(int x,int v){
op_array[x = m + x] += v;
while(x) operation();
}
- 如果维护区间和,那么
sum[i] = a[i << 1] + a[i << 1 | 1];
//如果单纯维护区间和,那么可以压行:
void update(int p, int k){ for (p += m; p; p >>= 1) sum[p] += k; }
- 如果维护区间最小值,那么
minn[i] = min(minn[i << 1], minn[i << 1 | 1]),
minn[i << 1] -= minn[i], minn[i << 1 | 1] -= minn[i];
- 如果维护区间最大值,那么
maxx[i] = max(maxx[i << 1], maxx[i << 1 | 1]),
maxx[i << 1] -= maxx[i], maxx[i << 1 | 1] -= maxx[i];
4.区间查询
如何进行区间查询?我们继续二进制表示入手,寻找查询的规律。
在实际的查询中,我们采取扩增左右区间端点的方式进行查询,即:将闭区间转换为开区间查询。
我们以下图为例:假设要查询的区间为,那么首先转换为开区间,我们可以发现变为开区间之后,的兄弟结点必在区间之内,的兄弟结点必在区间内;根据这个规律我们可以总结:
对于待查区间:
- 如果是左儿子,则其兄弟结点必位于区间之内;
- 如果是右儿子,则其兄弟结点必位于区间之内;
- 查询的终止条件:两个结点同为兄弟;
- 以上结论,对于任意层的结点均成立。
我们通过例子来模拟这个过程:

在如图所示的线段树中,假设我们要查询区间,那么步骤如下:
-
闭区间改开区间,改为查询,扩增至;
-
判断:左端点是左儿子,那么其兄弟必位于区间内,累加
判断:右端点是右儿子,那么其兄弟必位于区间内,累加 -
缩小区间(向根结点缩):
-
判断:左端点是左儿子,那么其兄弟必位于区间内,累加
判断:右端点是左儿子,不做操作; -
缩小区间(向根结点缩):
-
此时和同为兄弟,因此终止查询。
我们可以总结出区间查询的步骤:
- 闭区间改开区间
- 判断当前区间左端点是否是左儿子,如果是,则向累加器中累加兄弟结点;
判断当前区间右端点是否为右儿子,如果是,则向累加器中累加兄弟结点; - 端点变量处理操作:
- 循环执行的步骤,直到和同为兄弟结点(此时不终止会导致重复计算)
如何判断是否为左子节点?我们很容易观察到左右子节点共同的特征:左子节点最低位为 ,右子节点最低位为,那么我们可以通过以下操作的真值判断左右子节点
- 判断左子节点:
∼l & 1
l % 2 == 0
的意思 - 判断右子节点:
r & 1
r % 2 == 1
的意思
对于取兄弟结点的值则可以通过与异或求得:
- 左子节点求兄弟结点:
l xor 1
- 右子节点求兄弟结点:
r xor 1
TODO 文档先写到这里,先去看一代码,理解清楚后再继续研究
2023.01.05
https://blog.csdn.net/qcwlmqy/article/details/95938283
https://blog.csdn.net/weixin_43823476/article/details/92833064
https://www.acwing.com/blog/content/25173/
https://blog.csdn.net/yanweiqi1754989931/article/details/117575178
https://zhuanlan.zhihu.com/p/29876526
https://zhuanlan.zhihu.com/p/29937723
HDU 1166 敌兵布阵 单点更新,区间查询
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2022-01-05 AcWing 320 能量项链
2016-01-05 试题识别与生成