Luogu P4198 / bzoj 2957 楼房重建 线段树
程序员文章站
2022-07-13 11:58:15
...
小A的楼房外有一大片施工工地,工地上有N栋待建的楼房。每天,这片工地上的房子拆了又建、建了又拆。他经常无聊地看着窗外发呆,数自己能够看到多少栋房子。
为了简化问题,我们考虑这些事件发生在一个二维平面上。小A在平面上(0,0)点的位置,第i栋楼房可以用一条连接(i,0)和(i,Hi)的线段表示,其中Hi为第i栋楼房的高度。如果这栋楼房最高点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。
施工队的建造总共进行了M天。初始时,所有楼房都还没有开始建造,它们的高度均为0。在第i天,建筑队将会将横坐标为Xi的房屋的高度变为Yi(高度可以比原来大—修建,也可以比原来小—拆除,甚至可以保持不变—建筑队这天什么事也没做)。请你帮小A数数每天在建筑队完工之后,他能看到多少栋楼房?
分析:
建楼的图大概这样……瞎画的
可以发现,斜率是该楼是否可见的关键。(好像是废话)(以下的“大”、“小”都指斜率)
现在可以大体想出做法:
每更改一次,看更改处的左边是否有比它大的,有的话此次更改对答案无贡献;没有的话再看右边,右边比它大的才可见。
具体要用到线段树优化。
维护区间最大值和区间上升序列长度即可。#include<bits/stdc++.h>
using namespace std;
int n,m;
double a[100010];
struct building{
double mx;//区间最大值
int len;//区间上升序列长度
}t[400010];
void pushup1(int x)
{
t[x].mx=max(t[x<<1].mx ,t[x<<1|1].mx);//维护最大值
}
int pushup2(double mxx,int x,int l,int r)//关键部分
{
if(t[x].mx<=mxx) return 0;//对答案无贡献
if(a[l]>mxx) return t[x].len;
if(l==r) return a[l]>mxx;//若>,返回1;若<=,返回0
int s1=x<<1,s2=x<<1|1;//两个子区间
int mid=(l+r)>>1;
if(t[s1].mx<=mxx) return pushup2(mxx,s2,mid+1,r);
else return pushup2(mxx,s1,l,mid)+t[x].len-t[s1].len;
}
void change(int x,int l,int r,int a,int b)
{
if(l==r && l==a)
{
t[x].mx=(double)b/a;//斜率
t[x].len=1;
return;
}
int mid=(l+r)>>1;
if(a<=mid) change(x<<1,l,mid,a,b);
else change(x<<1|1,mid+1,r,a,b);//递归
pushup1(x);//维护区间最值
t[x].len=t[x<<1].len+pushup2(t[x<<1].mx,x<<1|1,mid+1,r);//维护区间上升序列长度
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[x]=(double)y/x;//斜率
change(1,1,n,x,y);//更改
printf("%d\n",t[1].len);
}
return 0;
}