洛谷P4198 楼房重建【线段树求前缀最大值个数】

题目描述:

小A在平面上(0,0)点的位置,第i栋楼房可以用一条连接(i,0)和(i,Hi)的线段表示,其中Hi为第i栋楼房的高度。如果这栋楼房上任何一个高度大于0的点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。

施工队的建造总共进行了M天。初始时,所有楼房都还没有开始建造,它们的高度均为0。在第i天,建筑队将会将横坐标为Xi的房屋的高度变为Yi(高度可以比原来大,也可以比原来小,甚至可以保持不变—建筑队这天什么事也没做)。请你帮小A数数每天在建筑队完工之后,他能看到多少栋楼房?
1<=Xi<=N,1<=Yi<=10^9,N,M<=100000

题目描述:

转成斜率之后就是求前缀最大值的个数(连续上升子序列长度)
单点修改,求值与前面的区间有关,考虑线段树。
如果记录区间最大值mx和前缀最大值的个数len,考虑如何合并两个区间:
就是左儿子的长度+在前面的最大值为mx[左儿子]的情况下右儿子的前缀最大值个数。

后面的东西相当于询问一个query(l,r,k),在前面最大值为k的情况下[l,r]的前缀最大值个数。
如果k>mx[l,mid],则答案为query(mid+1,r,k)
否则答案为query(l,mid,k)+query(mid+1,r,mx[左儿子]),而这个式子的右半部分与k无关,可以记录,实际上就是len[当前节点]-len[左儿子]。

一次修改之后需要重新维护log个节点,而维护一个节点相当于一次query,所以复杂度是nlog2n的。
答案就为len[1]

PS:此题可以加强为区间询问[l,r]能看到多少栋楼房,跨过mid时先求左半边的答案,返回长度和最大值,再讲最大值带入右半区间求解,当线段树区间被询问区间完全覆盖时,与上面的query一样用log的时间求解,所以复杂度仍然是log2的。

Code:

#include
#define maxn 100005
using namespace std;
int n,m,len[maxn<<2];
double mx[maxn<<2];
int query(int i,int l,int r,double k){
     
	if(mx[i]<=k) return 0;
	if(l==r) return 1;
	int mid=(l+r)>>1;
	if(k>=mx[i<<1]) return query(i<<1|1,mid+1,r,k);
	else return query(i<<1,l,mid,k)+len[i]-len[i<<1];
}
void insert(int i,int l,int r,int x,double k){
     
	if(l==r) {
     len[i]=1,mx[i]=k;return;}
	int mid=(l+r)>>1;
	if(x<=mid) insert(i<<1,l,mid,x,k);
	else insert(i<<1|1,mid+1,r,x,k);
	mx[i]=max(mx[i<<1],mx[i<<1|1]);
	len[i]=len[i<<1]+query(i<<1|1,mid+1,r,mx[i<<1]);
}
int main()
{
     
	scanf("%d%d",&n,&m);
	int x,y;
	while(m--){
     
		scanf("%d%d",&x,&y);
		insert(1,1,n,x,1.0*y/x);
		printf("%d\n",len[1]);
	}
}

你可能感兴趣的:(线段树)