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;
}

 

转载于:https://www.cnblogs.com/Never-mind/p/8907156.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值