【学习笔记】线段树维护单调栈

线段树维护单调栈

在经过一晚上和某考试题的奋斗后,我终于确定了那道题不能用线段树维护单调栈做,同时对这个算法有了更深的理解。

前言:

众所周知,线段树啥都能干。

求出最长上升/下降子序列,肯定可以 O(n) 单调栈跑一遍。但是如果套上单点修改和多次询问,O(n2) 的复杂度可能就吃不消了,所以,用线段树来求出这个最长上升/下降子序列。

实现:

我们在线段树上记录区间最大值 max,和一个最长递增序列长度 len

考虑如何去写 pushup

因为在经过左区间后,当前的最大值要么没变,要么变成了左区间的最大值,可以发现:只需要求出右区间大于左区间最大值的单调递增序列的最长长度即可

所以,只要维护右区间的 len。然后考虑怎么求出线段树上一个节点(序列上一个区间),比某个值大的最长递增序列长度。

引入一个 calc函数,分三种情况:

  1. 区间长度为 1:只需要判断这个值是否大于 data
  2. 左区间最大值大于 data:递归左区间,加上右区间的 len
  3. 左区间最大值小于等于 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 函数是 O(logn) 的,每次 pushup 时调用,为 O(log2n),总复杂度是 O(nlog2n) 的。

例题:

P4198 楼房重建

每座楼房是否能看见取决于斜率,通过斜率的单调递增来求出 len

其实就是求整个 1n 区间中从第一项开始,每一个大于前一项的必选,小于等于前一项的必须不选,所的得到的序列长度。

直接上板子就行了。

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;
}
posted @   TSTYFST  阅读(287)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示