您的位置:首页 > 其它

bzoj 2957 楼房重建(线段树)

2018-03-29 22:18 465 查看
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: