【学习笔记】吉司机线段树
本文同步在洛谷博客上进行发表。
这是一篇刚开始学习线段树的小白都能看懂的良心学习笔记!
前置知识:含有懒标记的线段树(没别的了)。
总述
什么是吉司机线段树?
就是维护区间最值和区间历史最值的线段树,它的名字来源于吉如一老师,他在 201620162016 年发表了一篇集训队论文。
不过为啥这位老师被称为吉司机我也不知道……
废话不多说,马上进入正题吧。
正题
例题:Luogu 6242
读完题我们发现,一般线段树题目只需要用到一个数组,而这道题用了两个,多出来了个 BBB 数组,是不是要拆成两棵树?
通过分析已知的所有关于 BBB 数组的条件,我们找出的答案是不用!
- BBB 数组最初和 AAA 数组完全相同
- 每次操作后,将 BBB 数组更新,使 Bi=max(Bi,Ai)B_i = \max(B_i,A_i)Bi=max(Bi,Ai)
这里我给出了两个条件,条件 111 是 BBB 数组的初始化,条件 222 是 BBB 数组的更新。显然可得:BiB_iBi 是出现过的所有 AiA_iAi 的最大值。
得出结论:BBB 数组维护 AAA 数组的历史最大值,两者公用一棵线段树。
题干分析完之后,让我们来分析一下操作。
操作号 | 简述 |
---|---|
111 | 区间加法 |
222 | 区间修改为最小值 |
333 | 区间求和 |
444 | 区间查询最大值 |
555 | 区间查询历史最大值中的最大值 |
接下来,我会分模块讲解(一定要往下看哟,有代码 /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];
- sumsumsum:区间内所有元素的和,在操作 333 时会用到。
- maxstmaxstmaxst:区间内的最大值,在操作 222 和操作 444 时会用到。
- maxndmaxndmaxnd:区间内的严格次大值,在操作 222 时会用到。
- maxhismaxhismaxhis:区间内的历史最大值,在操作 555 时会用到。
- maxnummaxnummaxnum:区间内最大值出现的次数,在更新区间和时会用到。
- lll,rrr:区间左右端点,记录可以减少码量。
- lazy1lazy1lazy1:维护 AAA 数组中最大值加法加的最多的那一次的懒标记。
- lazy2lazy2lazy2:维护 BBB 数组中最大值加法加的最多的那一次的懒标记。
- lazy3lazy3lazy3:维护 AAA 数组中除了最大值之外所有元素加法加的最多的那一次的懒标记。
- lazy4lazy4lazy4:维护 BBB 数组中除了最大值之外所有元素加法加的最多的那一次的懒标记。
- clear()clear()clear():清空区间内的懒标记,使代码整体更加模块化。
注:
- 为了方便,接下来我会把严格次大值称为次大值,但是读者勿忘它的真实含义!
- 我后文会将 sumsumsum,maxstmaxstmaxst,maxndmaxndmaxnd,maxnummaxnummaxnum 称为四大天王(还是想偷懒)。
我们运用一些 #define
来减少码量。
#define sum(p) tree[p].sum
#define max1(p) tree[p].maxst
#define max2(p) tree[p].maxnd
#define maxhis(p) tree[p].maxhis
#define cnt(p) tree[p].maxnum
#define l(p) tree[p].l
#define r(p) tree[p].r
#define lzy1(p) tree[p].lazy1
#define lzy2(p) tree[p].lazy2
#define lzy3(p) tree[p].lazy3
#define lzy4(p) tree[p].lazy4
看着感觉像在增加码量,不过我保证,最终码量一定会减少的qwq!
我试了试,加了这坨 #define
总码量 3.54KB,不加 3.95KB。
建树
递归的过程中,记录区间左右端点。
如果递归到了叶子结点(即 l=rl = rl=r),初始化四大天王和区间最大值的个数。由于区间里只有 111 个元素 AlA_lAl,所以把区间最大值出现的次数设为 111。四大天王中除了区间次大值,都初始化为 AlA_lAl。由于没有第二个数了,就暂且将区间次大值初始化为 -INF\text{-INF}-INF,这样是个数就能比它大,方便更新(如果初始化的不够小,比 AAA 数组的最小值 −5×108-5 \times 10^8−5×108 大,就会造成无法更新的局面,一定要注意)。
void build(int p, int l, int r)
{l(p) = l, r(p) = r;if(l == r) { sum(p) = max1(p) = maxhis(p) = a[l], max2(p) = -INF, cnt(p) = 1; return; }int mid = (l + r) >> 1; build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r), pushup(p);
}
向上更新
pushup()
会更新四大天王及区间最大值的出现次数。
区间最大值肯定是两个子区间最大元素中大的那一个,区间历史最大值同理。
区间和是两个子区间的和相加的结果。
对于区间最大值的出现次数:
当左子区间最大元素和右子区间最大元素相等时,它们父区间的最大元素出现次数就是两个子区间相加的结果。
否则,它们父区间的最大元素出现次数等于左右两个子区间中区间最大值大的那个区间的最大元素出现次数。
对于区间次大值:
当左子区间最大元素和右子区间最大元素相等时,它们父区间的次大元素等于两个子区间次大元素中大的那一个。
否则,它们父区间的次大元素等于最大元素小的那个子区间的最大元素和另一个子区间的次大元素中的最大值。
void pushup(int p)
{max1(p) = max(max1(p << 1), max1(p << 1 | 1));maxhis(p) = max(maxhis(p << 1), maxhis(p << 1 | 1));sum(p) = sum(p << 1) + sum(p << 1 | 1);if(max1(p << 1) == max1(p << 1 | 1)) max2(p) = max(max2(p << 1), max2(p << 1 | 1)), cnt(p) = cnt(p << 1) + cnt(p << 1 | 1);if(max1(p << 1) > max1(p << 1 | 1)) max2(p) = max(max1(p << 1 | 1), max2(p << 1)), cnt(p) = cnt(p << 1);if(max1(p << 1) < max1(p << 1 | 1)) max2(p) = max(max1(p << 1), max2(p << 1 | 1)), cnt(p) = cnt(p << 1 | 1);
}
下传懒标记
由于这道题维护了 444 个懒标记,下传懒标记时会比较复杂,为了方便理解,我们把下传懒标记的操作拆成 222 个函数:update()
和 pushdown()
(其实要论关系,update()
其实是 pushdown()
的子函数)。
先来看看 update()
。
- l1l1l1 是区间最大值加上的数。
- l2l2l2 是区间历史最大值加上的数。
- l3l3l3 是区间非最大值加上的数。
- l4l4l4 是区间历史非最大值加上的数。
更新区间和:把区间里的最大值个数乘上 l1l1l1,其它数的个数乘上 l3l3l3,它们的和就是区间和新增的部分。
更新区间次大值:如果区间不同值个数并非一,那么加上 l3l3l3(如果不同值个数为一,则不存在次大值,即次大值为 -INF\text{-INF}-INF)。
接下来是 444 个懒标记的更新,注意更新的顺序!
其余变量和代码一起理解,也就不是那么难了。
void update(int p, int l1, int l2, int l3, int l4)
{sum(p) += (l1 * cnt(p) + l3 * (rson(p) - lson(p) + 1 - cnt(p)));maxhis(p) = max(maxhis(p), max1(p) + l2);max1(p) += l1;if(max2(p) != -INF) max2(p) += l3;lzy2(p) = max(lzy2(p), lzy1(p) + l2), lzy1(p) += l1;lzy4(p) = max(lzy4(p), lzy3(p) + l4), lzy3(p) += l3;
}
下面该讲 pushdown()
了:
对于这题的 pushdown()
,我们需要分两种情况讨论。
众所周知,一个区间可以拆成两个子区间(左右儿子),对于一个子区间,如果其包含的最大元素比另一子区间包含的最大元素大,那么父区间要以更新最大值的礼仪来接待它,否则就按照更新非最大值的礼仪来接待它。
详见代码,注意懒标记用过之后要清零。
void pushdown(int p)
{int maxn = max(max1(p << 1), max1(p << 1 | 1));if(max1(p << 1) == maxn) update(p << 1, lzy1(p), lzy2(p), lzy3(p), lzy4(p));else update(p << 1, lzy3(p), lzy4(p), lzy3(p), lzy4(p));if(max1(p << 1 | 1) == maxn) update(p << 1 | 1, lzy1(p), lzy2(p), lzy3(p), lzy4(p));else update(p << 1 | 1, lzy3(p), lzy4(p), lzy3(p), lzy4(p));tree[p].clear();
}
坑点:maxnmaxnmaxn 要在函数最开始记录!不能每次在判断时比较两个子区间包含的最大元素大小! 我因为这个,一直 Unaccepted 了一个月,花了两个晚上去食堂吃饭的时间,还因此瘦了 222 斤 /kk
操作 111
正常 modify()
操作,如果区间在待更新的范围内,那么把所有数都加上 kkk ,即 update(p, k, k, k, k)
。
void modify1(int p)
{if(l(p) > y || r(p) < x) return;if(l(p) >= x && r(p) <= y) { update(p, k, k, k, k); return; }pushdown(p), modify1(p << 1), modify1(p << 1 | 1), pushup(p);
}
操作 222
在操作 111 的基础上有一些修改。
如果区间最大值都比 kkk 小,那么无需更新,直接 return
。
如果合法,还要进一步分类讨论:
如果 max2≤k≤max1max2 \le k \le max1max2≤k≤max1,则只需要更新 max1max1max1,把
update()
中的 l1l1l1 和 l2l2l2 都设置为 k−max1(p)k - max1(p)k−max1(p)(因为update()
是加法操作,所以加上相反数等于减去该数,最终就能达到更新为 kkk 的效果了)。否则,还需要向下推进,直到符合上面的那种情况才停止。
void modify2(int p)
{if(l(p) > y || r(p) < x || max1(p) <= k) return;if(l(p) >= x && r(p) <= y && max2(p) < k) { update(p, k - max1(p), k - max1(p), 0, 0); return; }pushdown(p), modify2(p << 1), modify2(p << 1 | 1), pushup(p);
}
询问操作
很常规,就不放代码了。
这里说几个其中的坑点:
- 求最大值时判断非法情况一定要返回极小值!
- 求和操作的非法情况返回 000。
后记
这题要用 long long
,给出简单证明:
因为 1≤n,m≤5×1051 \le n,m \le 5 \times 10^51≤n,m≤5×105,−5×108≤Ai≤5×108-5 \times 10^8 \le A_i \le 5 \times 10^8−5×108≤Ai≤5×108,所以如果操作 333 中的 l=1,r=nl=1,r=nl=1,r=n,那么结果的取值范围是 −2.5×1014≤ans≤2.5×1014-2.5 \times 10^{14} \le ans \le 2.5 \times 10 ^ {14}−2.5×1014≤ans≤2.5×1014,显然需要用到长整型。
但是细心的读者可能会看出我的所有代码都只有 int
的身影,这是因为我太懒,直接 #define int long long
了。大家写代码可不要被迷惑了哦!
希望我的文章能够帮到大家!
【学习笔记】吉司机线段树相关推荐
- 势能线段树/吉司机线段树-我没有脑子
势能线段树/吉司机线段树 BZOJ3211 花神游历各国 BZOJ5312 冒险 BZOJ4355 Play with sequence BZOJ4695 最假女选手 \(A_i = max(A_i, ...
- 2020ICPC(南京) - Just Another Game of Stones(吉司机线段树+博弈)
题目链接:点击查看 题目大意:给出一个长度为 nnn 的数列 aaa,现在需要执行 mmm 次操作,每次操作分为两种类型: 1lrx1 \ l \ r \ x1 l r x:对于所有 i∈[l,r]i ...
- HDU - 5306 Gorgeous Sequence(吉司机线段树)
题目链接:点击查看 题目大意:给出 1 ~ n 的区间以及 m 次操作,每次操作分为三种形式: 0 l r val:对于区间 [ l , r ] ,a[ i ] = min ( a[ i ] , va ...
- P6242-[模板]线段树3【吉司机线段树】
正题 题目链接:https://www.luogu.com.cn/problem/P6242 题目大意 给出一个长度为nnn的序列aaa,mmm次要求支持操作 区间加上一个值kkk 区间所有aia_i ...
- P7560-[JOISC 2021 Day1]フードコート【吉司机线段树】
正题 题目链接:https://www.luogu.com.cn/problem/P7560 题目大意 有 n n n个队列,要求支持操作: 往 [ L , R ] [L,R] [L,R]的队列中插入 ...
- 势能线段树(吉司机线段树)专题
势能线段树(吉司机线段树)专题 势能线段树在近期训练时遇到了好几次,但是由于本人太懒一直没补完,结果ICPC网络赛还真就出了一道势能线段树Orz--结果当然是没做出来--痛定思痛,这回把之前欠的一块儿 ...
- 数据结构专题-学习笔记:李超线段树
数据结构专题 - 学习笔记:李超线段树 1. 前言 2. 详解 3. 应用 4. 总结 5. 参考资料 1. 前言 本篇博文是博主学习李超线段树的学习笔记. 2020/12/21 的时候我在 线段树算 ...
- 2022.08.21 吉司机线段树略讲
Interpretation \color{green}{\texttt{Interpretation}} Interpretation 吉司机线段树(A.K.A. 势能线段树),个人觉得,就是对普通 ...
- 区块链学习笔记15——ETH状态树
区块链学习笔记15--ETH状态树 学习视频:北京大学肖臻老师<区块链技术与应用> 笔记参考:北京大学肖臻老师<区块链技术与应用>公开课系列笔记--目录导航页 引入 要实现的功 ...
- 区块链学习笔记16——ETH交易树和收据树
区块链学习笔记16--ETH交易树和收据树 学习视频:北京大学肖臻老师<区块链技术与应用> 笔记参考:北京大学肖臻老师<区块链技术与应用>公开课系列笔记--目录导航页 交易树和 ...
最新文章
- SQL SERVER 触发器示例
- 2010后的经济增长点再研究
- 三级计算机系统是什么情况,三级PC技术: 计算机的组成和分类
- 不一样的图片加载方式
- linux内核字符串逆序,Linux内核中常用字符串函数实现
- java记事本课程设计,java记事本课程设计
- redis的密码验证,及哨兵的相关配置
- Docker 学习资料
- 上海通信管理局启动对手机清理软件问题调查
- 2022年建筑架子工(建筑特殊工种)考试资料及建筑架子工(建筑特殊工种)新版试题
- Sql Server 随机抽样方法
- VSCode-工控机远程开发 之(一) VSCode无法连接扩展商店
- TP5框架的多图片上传返回不显示问题
- 学编程买什么类型的电脑适合?从预算到配置,给你安排的明明白白!
- cocos 从零开始 实现华容道 2.2.2版本
- go源码阅读——malloc.go
- 读书笔记:多智能体机器学习(二)
- 万豪旅享家旗下万怡酒店品牌落子江苏江阴
- vue3 使用Element Plus <script lang=“ts“ setup>加上lang=“ts“后编译错误
- jlink 烧写norflash方法
热门文章
- 读书笔记:《领导变革》
- 读后感系列3:《人类简史》尤瓦尔·赫拉利(一)
- 为什么html中图片显示不出来,网页图片显示不出来怎么办
- 金融行业数据容灾架构中的数据复制技术
- 如何裁剪动图的边框?教你一键在线裁剪动图
- 快速排序qsort函数的compar参数
- Linux编译DuiLib库报error: no matches converting function ‘ItemComareFunc’ to type ‘__compar_d_fn_t错误解决
- 微信群二维码活码生成源码
- tableau两个不同的图合并_tableau两个不同的图合并_Tableau数据源详解
- Linux安装iptables防火墙