BZOJ2957: 楼房重建
【传送门:BZOJ2957】
简要题意:
给出一个平面直角坐标系,有一个人站在(0,0)处,x轴的取值范围为1<=x<=n
有m个操作,每个操作输入x,y,表示在(x,0)处建一座楼,高y,如果(x,0)处本来没有楼则看作新建,否则看作改造
对于一座楼房,它能被看到当且仅当它的最高点与(0,0)的连线没有与其他楼有交点
求出每次操作后,这个人能看到的楼房数
题解:
线段树好题
对于一个楼房被看到,可以看作它与(0,0)的连线的斜率比前面所有的斜率都大
那么我们可以把每次操作都当成单点修改,然后求整段区间从第一个楼房开始斜率递增所得到的楼房数
设mx为每个区间中最大的斜率,c为每个区间从左端点开始能看到的楼房数
显然我们需要在修改的时候维护线段树
对于一段区间,显然左子区间的c值的贡献一定全部在整段区间的c值中,所以我们只要对右子区间进行处理
如果当前左子区间的最大值>=右子区间的最大值,那么右子区间对整段区间是没有贡献的
不然则在右子区间中找出以第一个大于左子区间的最大值的楼房,然后贡献为从这个楼房往后得到的楼房数(包括这个楼房)
就这样,注意一下精度就可以了
参考代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<cstdlib> #define eps 1e-10 using namespace std; struct trnode { int l,r,lc,rc,c; double mx; }tr[210000];int trlen; void bt(int l,int r) { int now=++trlen; tr[now].l=l;tr[now].r=r; tr[now].lc=tr[now].rc=-1; tr[now].c=0;tr[now].mx=0.0; if(l<r) { int mid=(l+r)/2; tr[now].lc=trlen+1;bt(l,mid); tr[now].rc=trlen+1;bt(mid+1,r); } } int ans; void findd(int now,double d) { if(tr[now].l==tr[now].r){ans++;return ;} int lc=tr[now].lc,rc=tr[now].rc; if(tr[lc].mx<=d) findd(rc,d); else ans+=tr[now].c-tr[lc].c,findd(lc,d); } void follow(int now) { int lc=tr[now].lc,rc=tr[now].rc; tr[now].c=tr[lc].c;tr[now].mx=tr[lc].mx; if(tr[rc].mx-tr[lc].mx>eps) { tr[now].mx=tr[rc].mx; ans=0;findd(rc,tr[lc].mx); tr[now].c+=ans; } } void change(int now,int x,double d) { if(tr[now].l==tr[now].r) { tr[now].mx=d; tr[now].c=1; return ; } int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)/2; if(x<=mid) change(lc,x,d); else change(rc,x,d); follow(now); } int main() { int n,m; scanf("%d%d",&n,&m); trlen=0;bt(1,n); for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); change(1,x,double(y)/double(x)); printf("%d\n",tr[1].c); } return 0; }
渺渺时空,茫茫人海,与君相遇,幸甚幸甚