BZOJ2957 楼房重建(分块/线段树上二分)

题目

这些事件发生在一个二维平面上,待建的楼房的横坐标在1到N(1<=N<=1e5)之间。

小A在平面上(0,0)点的位置,第i栋楼房可以用一条连接(i,0)和(i,Hi)的线段表示,其中Hi为第i栋楼房的高度。

如果这栋楼房上任何一个高度大于0的点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。

施工队的建造总共进行了M(1<=M<=1e5)天。初始时,所有楼房都还没有开始建造,它们的高度均为0。在第i天,建筑队将会将横坐标为Xi(1<=Xi<=N)的房屋的高度变为Yi(1<=Yi<=1e9)(高度可以比原来大---修建,也可以比原来小---拆除,甚至可以保持不变---建筑队这天什么事也没做)。

请你帮小A数数每天在建筑队完工之后,他能看到多少栋楼房?

输出共m行,第i行一个整数,表示第i天过后小A能看到的楼房有多少栋

思路来源

https://www.cnblogs.com/ljh2000-jump/p/6038304.html 分块

http://hzwer.com/6746.html 线段树二分

题解

①分块,分成根号n块,每个块内维护一个顺序扫的严格上升子序列和块内最大值,

改的时候改当前块内的,查的时候对于每个块二分统计块内对答案贡献的个数

 

②线段树二分,每个区间p维护区间最值mx和只考虑这个区间时的答案ans,根区间的ans即为所求

修改时一并更新,cal(p,v)用于计算如果当前值是v,在p区间内v后面还能有几个数

 

一个显而易见的结论是,这种数字的值是单调递增的。

我们修改一个数只会对这个数后面的数造成影响。

考虑线段树划分出来的若干线段。这里有两种情况:

1、某个线段中的最大值小于等于修改的数,那么这个线段的贡献为0,无需处理

2、否则我们将这个线段分成两个并单独考虑,

如果左侧的最大值大于修改的数,那么是不影响右侧的贡献的,只需递归处理左侧;

否则就变成了第一种情况

复杂度O(n(logn)^2)

——http://hzwer.com/6746.html 

代码1(分块)

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<cmath> 
using namespace std;
const int N=1e5+10,M=400;
const double eps=1e-9;
struct node
{
	int cnt;
	double a[M],mx;
	node():cnt(0),mx(0){
	}
}e[M];
int n,m,x,y,blk,up,in[N],l[N],r[N],L,R,mid,ans;
double now[N],h;
int main()
{
	scanf("%d%d",&n,&m);
	blk=sqrt(n);
	for(int i=0;i<n;++i)
	{
		in[i]=i/blk;
		l[i]=in[i]*blk;
		r[i]=min(n,l[i]+blk);
	}
	up=in[n-1];
	while(m--)
	{
		scanf("%d%d",&x,&y);
		x--;
		now[x]=1.0*y/(x+1.0);
		int &z=in[x];
		e[z].cnt=e[z].mx=0;
		for(int i=l[x];i<r[x];++i)
		{
			if(now[i]<=e[z].mx)continue;
			e[z].mx=now[i];
			e[z].a[++e[z].cnt]=e[z].mx;
		}
		h=0;ans=0;
		for(int i=0;i<=up;++i)
		{
			if(!e[i].cnt||e[i].mx<=h)continue;
			L=1,R=e[i].cnt;
			while(L<=R)
			{
				mid=(L+R)>>1;
				if(e[i].a[mid]>=h)R=mid-1;
				else L=mid+1;
			}
			ans+=e[i].cnt-L+1;
			h=e[i].a[e[i].cnt];
		}
		printf("%d\n",ans);
	}
	return 0;
}

代码2(线段树二分)

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<cmath> 
using namespace std;
const int N=1e5+10;
int n,m,x,y;
struct node
{
	int ans,l,r;
	double mx;
}e[N*4];
void build(int p,int l,int r)
{
	e[p].l=l,e[p].r=r;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
}
int cal(int p,double v)//该区间内>v的值有多少个
{
	int l=e[p].l,r=e[p].r;
	if(l==r)return e[p].mx>v;
	if(e[p<<1].mx<=v)return cal(p<<1|1,v);//右子树里找 
	return e[p].ans-e[p<<1].ans+cal(p<<1,v);//左+完整右子树 
	//此处的右子树 是需要考虑完整e[p]区间 左侧最大值<=右侧的所有ans的 
	//所以用e[p].ans-e[p<<1].ans 而不是e[p<<1|1].ans 毕竟二者不等价 
} 
void upd(int p,int pos,double v)
{
	int l=e[p].l,r=e[p].r;
	if(l==r)
	{
		e[p].ans=1;
		e[p].mx=v;
		return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)upd(p<<1,pos,v);
	else upd(p<<1|1,pos,v);
	e[p].mx=max(e[p<<1].mx,e[p<<1|1].mx);
	e[p].ans=e[p<<1].ans+cal(p<<1|1,e[p<<1].mx);
}
int main()
{
	scanf("%d%d",&n,&m);
	build(1,1,n);
	while(m--)
	{
		scanf("%d%d",&x,&y);
		upd(1,x,1.0*y/x);
		printf("%d\n",e[1].ans);
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值