楼房重建
我
们
发
现
能
不
能
看
到
一
件
楼
房
就
要
看
前
面
有
没
有
比
它
斜
率
大
的
楼
房
我们发现能不能看到一件楼房就要看前面有没有比它斜率大的楼房
我们发现能不能看到一件楼房就要看前面有没有比它斜率大的楼房
而
看
数
据
范
围
,
是
可
以
存
斜
率
的
,
所
以
很
自
然
地
分
治
一
下
(
类
似
线
段
树
)
而看数据范围,是可以存斜率的,所以很自然地分治一下(类似线段树)
而看数据范围,是可以存斜率的,所以很自然地分治一下(类似线段树)
算
出
左
边
看
过
去
可
以
看
到
的
楼
房
,
算
出
右
边
可
以
看
到
的
楼
房
,
然
后
合
并
一
下
算出左边看过去可以看到的楼房,算出右边可以看到的楼房,然后合并一下
算出左边看过去可以看到的楼房,算出右边可以看到的楼房,然后合并一下
具
体
的
,
我
们
要
算
出
[
l
,
r
]
(
树
上
标
号
为
p
)
,
则
a
n
s
[
p
<
<
1
]
+
右
边
可
以
加
进
的
答
案
,
而
u
p
p
d
a
t
e
操
作
就
是
如
果
i
d
比
m
i
d
小
就
改
左
边
,
否
则
改
右
边
具体的,我们要算出[l,r](树上标号为p),则ans[p<<1]+右边可以加进的答案,而uppdate操作就是如果id比mid小就改左边,否则改右边
具体的,我们要算出[l,r](树上标号为p),则ans[p<<1]+右边可以加进的答案,而uppdate操作就是如果id比mid小就改左边,否则改右边
实
际
上
,
这
里
维
护
了
一
个
单
调
栈
(
不
同
之
处
在
于
这
个
单
调
栈
可
以
带
修
)
,
并
把
左
右
两
端
的
单
调
栈
合
并
,
合
并
就
是
左
边
单
调
,
右
边
求
出
可
加
入
总
单
调
栈
的
值
即
可
,
实际上,这里维护了一个单调栈(不同之处在于这个单调栈可以带修),并把左右两端的单调栈合并,合并就是左边单调,右边求出可加入总单调栈的值即可,
实际上,这里维护了一个单调栈(不同之处在于这个单调栈可以带修),并把左右两端的单调栈合并,合并就是左边单调,右边求出可加入总单调栈的值即可,
因
为
我
们
并
不
关
心
单
调
站
中
每
个
元
素
具
体
是
什
么
,
所
以
我
们
不
需
要
管
每
个
元
素
具
体
加
到
的
位
置
是
哪
里
,
我
们
只
需
要
维
护
最
大
值
和
栈
的
长
度
就
可
以
计
算
答
案
了
因为我们并不关心单调站中每个元素具体是什么,所以我们不需要管每个元素具体加到的位置是哪里,我们只需要维护最大值和栈的长度就可以计算答案了
因为我们并不关心单调站中每个元素具体是什么,所以我们不需要管每个元素具体加到的位置是哪里,我们只需要维护最大值和栈的长度就可以计算答案了
所
以
就
可
以
合
并
了
所以就可以合并了
所以就可以合并了
void uppdate(int l,int r,int p,int id,double val){
if(l==r&&r==id){
maxn[p]=val;ans[p]=1;//推到底直接更新
return;
}
int mid=(l+r)>>1;
if(id<=mid){uppdate(l,mid,p<<1,id,val);}
else uppdate(mid+1,r,p<<1|1,id,val);//根据id修改
maxn[p]=max(maxn[p<<1],maxn[p<<1|1]);//最大值就是左右两边的最大值
ans[p]=ans[p<<1]+query(mid+1,r,p<<1|1,maxn[p<<1]); //左边的肯定可以看见,右边需要查哪些可以合并,并加上限制maxn[p<<1]
}
u
p
p
d
a
t
e
后
再
合
并
uppdate后再合并
uppdate后再合并
具
体
地
,
如
果
m
a
x
n
[
p
<
<
1
]
<
=
m
x
,
所
以
左
边
不
可
能
会
有
可
以
看
得
见
的
,
所
以
只
用
查
找
右
边
,
如
果
m
a
x
n
[
p
<
<
1
∣
1
]
>
m
x
,
所
以
左
边
有
一
些
可
以
,
但
右
边
也
有
一
些
可
以
,
由
于
这
时
推
到
p
这
层
,
所
以
左
右
子
树
都
已
经
跟
新
完
,
所
以
a
n
s
[
k
]
=
a
n
s
[
k
<
<
1
]
+
a
n
s
[
k
<
<
1
∣
1
]
,
我
们
发
现
由
于
对
于
右
子
树
所
有
取
值
的
限
制
还
是
左
边
的
最
大
值
,
所
以
虽
然
限
制
发
生
变
化
,
但
不
大
于
左
子
树
的
最
大
值
,
所
以
对
于
右
边
来
说
,
限
制
不
变
,
还
是
左
子
树
的
最
大
值
,
所
以
右
边
可
以
的
还
是
可
以
,
不
可
以
的
还
是
不
可
以
具体地,如果maxn[p<<1]<=mx,所以左边不可能会有可以看得见的,所以只用查找右边,如果maxn[p<<1|1]>mx,所以左边有一些可以,但右边也有一些可以,由于这时推到p这层,所以左右子树都已经跟新完,所以ans[k]=ans[k<<1]+ans[k<<1|1],我们发现由于对于右子树所有取值的限制还是左边的最大值,所以虽然限制发生变化,但不大于左子树的最大值,所以对于右边来说,限制不变,还是左子树的最大值,所以右边可以的还是可以,不可以的还是不可以
具体地,如果maxn[p<<1]<=mx,所以左边不可能会有可以看得见的,所以只用查找右边,如果maxn[p<<1∣1]>mx,所以左边有一些可以,但右边也有一些可以,由于这时推到p这层,所以左右子树都已经跟新完,所以ans[k]=ans[k<<1]+ans[k<<1∣1],我们发现由于对于右子树所有取值的限制还是左边的最大值,所以虽然限制发生变化,但不大于左子树的最大值,所以对于右边来说,限制不变,还是左子树的最大值,所以右边可以的还是可以,不可以的还是不可以
int query(int l,int r,int p,double mx){/*mx 是double型的*/
if(maxn[p]<=mx){return 0;}
if(l==r){return maxn[p]>mx;}
int mid=(l+r)>>1,anss;
if(maxn[p<<1]<=mx){anss=query(mid+1,r,p<<1|1,mx);}
else anss=query(l,mid,p<<1,mx)+ans[p]-ans[p<<1];
return anss;
}
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,ans[N*4];
double maxn[N*4];
int query(int l,int r,int p,double mx){/*mx 是double型的*/
if(maxn[p]<=mx){return 0;}
if(l==r){return maxn[p]>mx;}
int mid=(l+r)>>1,anss;
if(maxn[p<<1]<=mx){anss=query(mid+1,r,p<<1|1,mx);}
else anss=query(l,mid,p<<1,mx)+ans[p]-ans[p<<1];
return anss;
}
void uppdate(int l,int r,int p,int id,double val){
if(l==r&&r==id){
maxn[p]=val;ans[p]=1;
return;
}
int mid=(l+r)>>1;
if(id<=mid){uppdate(l,mid,p<<1,id,val);}
else uppdate(mid+1,r,p<<1|1,id,val);
maxn[p]=max(maxn[p<<1],maxn[p<<1|1]);
ans[p]=ans[p<<1]+query(mid+1,r,p<<1|1,maxn[p<<1]);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
uppdate(1,n,1,x,(double)y/x);/*注意不要写double(y/x)*/
printf("%d\n",ans[1]);
}
}

本文探讨了一种解决楼房重建问题的方法,利用线段树存储斜率,通过分治策略计算可见楼房数量。文章详细介绍了如何使用单调栈进行左右合并,以及如何在更新操作中维护最大值和栈长度,从而高效地解决该问题。
263

被折叠的 条评论
为什么被折叠?



