本文同步在洛谷博客上进行发表。
这是一篇刚开始学习线段树的小白都能看懂的良心学习笔记!
前置知识:含有懒标记的线段树(没别的了)。
总述
什么是吉司机线段树?
就是维护区间最值和区间历史最值的线段树,它的名字来源于吉如一老师,他在 2016 2016 2016 年发表了一篇集训队论文。
不过为啥这位老师被称为吉司机我也不知道……
废话不多说,马上进入正题吧。
正题
例题:Luogu 6242
读完题我们发现,一般线段树题目只需要用到一个数组,而这道题用了两个,多出来了个 B B B 数组,是不是要拆成两棵树?
通过分析已知的所有关于 B B B 数组的条件,我们找出的答案是不用!
- B B B 数组最初和 A A A 数组完全相同
- 每次操作后,将 B B B 数组更新,使 B i = max ( B i , A i ) B_i = \max(B_i,A_i) Bi=max(Bi,Ai)
这里我给出了两个条件,条件 1 1 1 是 B B B 数组的初始化,条件 2 2 2 是 B B B 数组的更新。显然可得: B i B_i Bi 是出现过的所有 A i A_i Ai 的最大值。
得出结论: B B B 数组维护 A A A 数组的历史最大值,两者公用一棵线段树。
题干分析完之后,让我们来分析一下操作。
操作号 | 简述 |
---|---|
1 1 1 | 区间加法 |
2 2 2 | 区间修改为最小值 |
3 3 3 | 区间求和 |
4 4 4 | 区间查询最大值 |
5 5 5 | 区间查询历史最大值中的最大值 |
接下来,我会分模块讲解(一定要往下看哟,有代码 /se)。
吉司机线段树本质上就是带懒标记的线段树,所以代码和线段树的前两个模板题也是大同小异的。
都有啥?
struct SegTree {
};
void pushup()
void build()
void pushdown()
void modify()
// 此处省略N个modify……
int query()
// 此处省略N个query……
看着就码量惊人,所以我们要在保证代码可读性的基础上尝试减少码量,原因有二:
- 代码相对短,看着舒服(要不然会感觉像在写大 % 你)
- 由于这题代码不可避免的长(函数多,维护操作多),所以出错的概率也会比较高,错了之后调代码会令人崩溃。为了自己的心理健康着想,还是要把代码写的漂亮一点的。
具体怎样减少码量,我会在后文进行详细的描述。
线段树结构体
struct SegTree
{
int sum, maxst, maxnd, maxhis, maxnum, l, r;
int lazy1, lazy2, lazy3, lazy4;
void clear() {
lazy1 = lazy2 = lazy3 = lazy4 = 0; }
} tree[NR << 2];
- s u m sum sum:区间内所有元素的和,在操作 3 3 3 时会用到。
- m a x s t maxst maxst:区间内的最大值,在操作 2 2 2 和操作 4 4 4 时会用到。
- m a x n d maxnd maxnd:区间内的严格次大值,在操作