【学习笔记】线段树维护单调栈
线段树维护单调栈
在经过一晚上和某考试题的奋斗后,我终于确定了那道题不能用线段树维护单调栈做,同时对这个算法有了更深的理解。
前言:
众所周知,线段树啥都能干。
求出最长上升/下降子序列,肯定可以 单调栈跑一遍。但是如果套上单点修改和多次询问, 的复杂度可能就吃不消了,所以,用线段树来求出这个最长上升/下降子序列。
实现:
我们在线段树上记录区间最大值 max
,和一个最长递增序列长度 len
。
考虑如何去写 pushup
。
因为在经过左区间后,当前的最大值要么没变,要么变成了左区间的最大值,可以发现:只需要求出右区间大于左区间最大值的单调递增序列的最长长度即可。
所以,只要维护右区间的 len
。然后考虑怎么求出线段树上一个节点(序列上一个区间),比某个值大的最长递增序列长度。
引入一个 calc
函数,分三种情况:
- 区间长度为 :只需要判断这个值是否大于
data
。 - 左区间最大值大于
data
:递归左区间,加上右区间的len
。 - 左区间最大值小于等于
data
:左区间就没有用了,它产生不了贡献,直接递归右区间。
int Calc(int rt, double val){ if(tr[rt].l == tr[rt].r) return tr[rt].max > val; int mid = (tr[rt].l + tr[rt].r) >> 1; if(tr[lson(rt)].max > val) return Calc(lson(rt), val) + tr[rson(rt)].len; else return Calc(rson(rt), val); }
左区间会对右区间造成影响,pushup
的时候 len
难以合并,所以只更新右区间的 len
。
void Pushup(int rt){ tr[rt].max = max(tr[lson(rt)].max, tr[rson(rt)].max); tr[rson(rt)].len = Calc(rson(rt), tr[lson(rt)].max); }
考虑如何求出最长的递增序列长度,只记录了右区间的 len
,并且这东西合并很麻烦。然而我们有 calc
函数,直接查询 calc(1, 0.0)
就行了。
如果求递减序列,反过去就行了。
复杂度:
单次 calc
函数是 的,每次 pushup
时调用,为 ,总复杂度是 的。
例题:
P4198 楼房重建
每座楼房是否能看见取决于斜率,通过斜率的单调递增来求出 。
其实就是求整个 到 区间中从第一项开始,每一个大于前一项的必选,小于等于前一项的必须不选,所的得到的序列长度。
直接上板子就行了。
Code
#include<cstdio> #include<algorithm> using namespace std; const int MAXN = 1e5 + 10; int n, m; inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9'){ if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9'){ x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } struct Segment_Tree{ struct Tree{ int l, r; int len; double max; }tr[MAXN << 2]; inline int lson(int rt){ return rt << 1; } inline int rson(int rt){ return rt << 1 | 1; } int Calc(int rt, double val){ if(tr[rt].l == tr[rt].r) return tr[rt].max > val; int mid = (tr[rt].l + tr[rt].r) >> 1; if(tr[lson(rt)].max > val) return Calc(lson(rt), val) + tr[rson(rt)].len; else return Calc(rson(rt), val); } void Pushup(int rt){ tr[rt].max = max(tr[lson(rt)].max, tr[rson(rt)].max); tr[rson(rt)].len = Calc(rson(rt), tr[lson(rt)].max); } void Build(int rt, int l, int r){ tr[rt].l = l; tr[rt].r = r; if(l == r) return; int mid = (l + r) >> 1; Build(lson(rt), l, mid); Build(rson(rt), mid + 1, r); } void Update(int rt, int pos, int high){ if(tr[rt].l == tr[rt].r){ tr[rt].max = (double) high / pos; return; } int mid = (tr[rt].l + tr[rt].r) >> 1; if(pos <= mid) Update(lson(rt), pos, high); else Update(rson(rt), pos, high); Pushup(rt); } }S; int main(){ n = read(), m = read(); S.Build(1, 1, n); for(register int i = 1; i <= m; i++){ int x, y; x = read(), y = read(); S.Update(1, x, y); printf("%d\n", S.Calc(1, 0.0)); } return 0; }
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16685397.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理