bzoj 2957 楼房重建(线段树)

本文介绍了一种解决bzoj2957楼房重建问题的方法,通过调整楼高并使用线段树维护最长上升子序列,实现nlogn^2的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

bzoj 2957 楼房重建

这题是集训时讲到的,感觉不错就做了做;

先把每个楼的高度除以xi,这样得到的斜率ki,然后求一个从左往右开始左边优先选的最长上升序列;

首先我想到的做法是,用线段树来维护,每个点存当前区间内合法的那个序列的最小值和最大值以及长度,每次改完一个值一路update上去,update时先把左区间的答案加上去,再在右区间内找一段大于左区间合法序列的最大值的合法序列;

然后查找的时候我犯傻了。。。我是先找左区间,找完再以左区间最大值找右区间,这样t到飞起,复杂度好像n^2*logn?。。。

然后考虑减少重复计算次数,每个点记录lsum和rsum表示从左区间转移来的答案和从右区间转移来的答案

这样查找时如果能从左区间进行查找,那么直接加上右区间既定的答案就好;
否则直接从右区间查找;

这样正确性显然,复杂度nlogn^2

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<utility>
#define db double
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define mk(a,b) std::make_pair(a,b)
#define pr std::pair<int,double>
const int maxn=100005;
using std::max;
struct NODE{
    int lsum,rsum;
    db mv,lv;
}t[maxn*4];
db eps=1e-10;
bool iszero(db v){
    return fabs(v-0.0)<=eps;
}
pr search(int l,int r,int rt,db v){
    if(t[rt].lv-v>eps)
        return mk(t[rt].lsum+t[rt].rsum,t[rt].mv);
    if(l==r||t[rt].mv-v<eps)return mk(0,0.0);
    int mid=(l+r)>>1;
    int ls=rt<<1;
    int rs=rt<<1|1;
    if(t[ls].mv-v>eps){
        pr tp=search(lson,v);
        tp.first+=t[rt].rsum;
        tp.second=max(tp.second,t[rs].mv);
        return tp;
    }else{
        return search(rson,v);
    }
}
void updata(int rt,int l,int r){
    int ls=rt<<1;
    int rs=rt<<1|1;
    if(iszero(t[ls].lv)||iszero(t[rs].lv)){
        t[rt].lv=t[ls].lv+t[rs].lv;
        t[rt].mv=max(t[ls].mv,t[rs].mv);
        t[rt].lsum=t[ls].lsum+t[ls].rsum;
        t[rt].rsum=t[rs].lsum+t[rs].rsum;
        return;
    }
    int mid=(l+r)>>1;
    pr tp=search(rson,t[ls].mv);
    t[rt].lv=t[ls].lv;
    t[rt].lsum=t[ls].lsum+t[ls].rsum;
    t[rt].rsum=tp.first;
    t[rt].mv=max(t[ls].mv,tp.second);
}
void modify(int l,int r,int rt,int pos,int v){
    if(l==r){
        t[rt].lsum=0,t[rt].rsum=1,t[rt].lv=t[rt].mv=(db)v/pos;
        return;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)modify(lson,pos,v);
    else modify(rson,pos,v);
    updata(rt,l,r);
}
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int tc,tv;
        scanf("%d%d",&tc,&tv);
        modify(1,n,1,tc,tv);
        printf("%d\n",t[1].lsum+t[1].rsum);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值