洛谷4198 楼房重建(线段树)

神仙题。pb 说是套路题???

首先只有楼房顶有用,然后要斜率递增。

没事干了,上线段树。

肯定要记录区间单调队列的长度 (len)。当然把整个能看到的位置都记下来会更方便,然而复杂度就爆了。

顺便再来个区间最大值 (mx)

修改是单点,叶子很好搞。询问就是根节点的 (len)

难点就在 pushup。

(mx) 不管,只看 (len)

首先发现,最左边的和最大值都能看到。

pushup 时,首先左边的所有数都仍然能被看到,右边能看到的数的个数和左边的最大值有关,右边留下的只有大于左边最大值的数。

所以我们递归 pushup:

参数有:线段树节点编号 (o) 和能看到的数的下界 (lwr)(也就是只保留 (>lwr) 的数,然后再求看不看得到)

如果 (mx_ole lwr),整个区间都不能看到。

如果 (a_l>lwr),那么答案就是 (len_o)(能看到的数和没有这个下界没区别)。

如果 (mx_{lson}le lwr),那么左儿子全都没有,递归到右儿子 (pushup(rson,lwr))

否则左儿子的最大值肯定保留了下来,那么右儿子能看到的数与没有下界没有区别,个数是 (len_o-len_{lson})。递归到左儿子 (pushup(lson,lwr)+len_o-len_{lson})

这样 pushup 一次是 (O(log n))

时间复杂度 (O(mlog^2n))

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=400040;
#define ls o<<1
#define rs o<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
template<typename T>
inline void read(T &x){
	x=0;
	char ch=getchar();bool f=false;
	while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
	if(f) x=-x;
}
int n,m,len[MAXN];
double a[MAXN],mx[MAXN];
inline void pushup1(int o){
	mx[o]=max(mx[ls],mx[rs]);
}
int pushup2(int o,int l,int r,double lwr){
	if(a[l]>lwr) return len[o];
	if(mx[o]<=lwr) return 0;
	int mid=(l+r)>>1;
	if(mx[ls]<=lwr) return pushup2(rson,lwr);
	else return pushup2(lson,lwr)+len[o]-len[ls];
}
inline void pushup(int o,int l,int r){
	pushup1(o);
	int mid=(l+r)>>1;
	len[o]=len[ls]+pushup2(rson,mx[ls]);
}
void update(int o,int l,int r,int p,double v){
	if(l==r) return void((mx[o]=v,len[o]=1));
	int mid=(l+r)>>1;
	if(mid>=p) update(lson,p,v);
	if(mid<p) update(rson,p,v);
	pushup(o,l,r);
}
int main(){
	read(n);read(m);
	while(m--){
		int x,y;
		read(x);read(y);
		update(1,1,n,x,a[x]=1.0*y/x);
		printf("%d
",len[1]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/1000Suns/p/11976014.html