[bzoj2957] 楼房重建
题意:
数轴上有$n$个楼房,初始时高度都是0。有$m$次操作,每次将楼房$x$的高度变为$y$。
有一个人站在$(0,0)$,每次操作之后请你求出这个人能看见多少个楼房。
一个楼房能被看见当且仅当$(x,y)$到$(0,0)$的连线不会被任何楼房阻挡。
$n,m\leq 10^{5}$。
题解:线段树维护单调栈模板。
首先假设每个位置的权值是斜率,那么实际上就是求单调栈元素个数。
于是这就是一道线段树维护单调栈的模板题。
之前写过但不太清楚,现在再回顾一遍实现方法:
每个线段树节点维护两个值,$trmax$表示该区间权值最大值,$trsiz$表示该区间形成的单调栈的元素个数。
考虑单点修改$p$如何处理:
- 若$p$在右儿子中,那么不会影响左儿子,递归修改右儿子即可。(不会影响儿子指不会影响儿子在父亲单调栈中的贡献)
- 若$p$在左儿子中,那么在修改左儿子后可能会影响右儿子,此时我们需要设计一个$calc$操作来$pushup$。
$calc(l,r,val,k)$的含义是:当单调栈的初始值为$val$时,$[l,r]$这个区间形成的单调栈的元素个数。
那么$pushup$就可以写了:$trsiz(k)=trsiz(lson)+calc(mid+1,r,trmax(lson),rson)$。
考虑$calc$的值如何计算:
- 若$val>=trmax(lson)$,那么左儿子不会产生贡献,递归右儿子即可。答案为$calc(l,mid,val,lson)$。
- 若$val<trmax(lson)$,那么两个儿子都会产生贡献,答案为$calc(l,mid,val,lson)+calc(mid+1,r,val,rson)$。
但这样做的话每$pushup$一次可能要遍历整棵线段树,复杂度就是$O(n)$的,无法接受。
容易发现$calc(mid+1,r,val,rson)$这个值在$val<trmax(lson)$时实际上就是$k$的单调栈中右半部分的元素个数。
那么这个个数就等于$trsiz(k)-trsiz(lson)$。注意不是$trsiz(rson)$,这是没考虑左儿子时右儿子的贡献。
这样每次$pushup$的复杂度是$O(logn)$,总复杂度为$O(nlog^{2}n)$。
实际上所有线段树维护单调栈的题都是大同小异,区别可能只在于如何快速计算某点单调栈中右半部分的贡献。
代码:
#include<bits/stdc++.h> #define maxn 100005 #define maxm 500005 #define inf 0x7fffffff #define eps 1e-8 #define ll long long #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; double trmx[maxn<<2]; int trsz[maxn<<2]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline int qry(int l,int r,double v,int k){ //cout<<l<<" "<<r<<":"<<trmx[k]<<" "<<v<<endl; if(l==r) return trmx[k]>v; int mid=l+r>>1; if(v>trmx[k<<1]+eps) return qry(mid+1,r,v,k<<1|1); else return qry(l,mid,v,k<<1)+trsz[k]-trsz[k<<1]; } inline void upd(int l,int r,int x,double y,int k){ if(l==r){trmx[k]=y,trsz[k]=1;return;} int mid=l+r>>1; if(x<=mid) upd(l,mid,x,y,k<<1); else upd(mid+1,r,x,y,k<<1|1); trmx[k]=max(trmx[k<<1],trmx[k<<1|1]); trsz[k]=trsz[k<<1]+qry(mid+1,r,trmx[k<<1],k<<1|1); //cout<<l<<" "<<r<<"::::::"<<trmx[k]<<" "<<trsz[k]<<endl; } int main(){ int n=read(),Q=read(); while(Q--){ int x=read(),y=read(); upd(1,n,x,(double)((y+0.0)/(x+0.0)),1); printf("%d\n",trsz[1]); //fgx; } return 0; }