题解:P4198 楼房重建
考虑将每个点存为连接 \((0,0)\) 和 \((i,H_i)\) 的线段的斜率,那么每个可以被看到的点的斜率必须大于它左侧所有点的斜率。
所以我们就要统计有多少个点的斜率是前缀最大值,考虑线段树。
设 \(m_u\) 表示 \(u\) 区间的最大值,\(f(u)\) 表示 \(u\) 对应区间的答案,\(g(u,m)\) 表示 \(u\) 区间内所有 \(>m\) 的数构成的答案。在 pushup 的时候,显然有 \(f(u)=f(ls_u)+g(rs_u,m_{ls_u})\),即右区间选的数必须大于 \(m_{ls_u}\)。
考虑如何求 \(g(u,m)\),有两种情况:
- \(m_{ls_u}\le m\),那么左儿子的贡献一定为 \(0\),返回 \(g(rs_u,m)\) 即可。
- \(m_{ls_u}>m\),那么右儿子能贡献的点和原来一样,只需递归求解左儿子即可,返回 \(f(u)-f(ls_u)+g(ls_u,m)\)。
这样我们在求 \(g\) 的过程中只需向一个分支递归,其复杂度为 \(O(\log n)\),单次修改复杂度为 \(O(\log^2n)\),查询复杂度为 \(O(1)\)。
Code:
#include<iostream>
#define ls (x<<1)
#define rs (x<<1|1)
#define mid ((l+r)>>1)
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
#define per(i,l,r) for(int i=(l);i>=(r);i--)
using namespace std;
const int maxn=1e5+5;
double mx[maxn<<2];
int at[maxn<<2];
inline int getg(int l,int r,int x,double m){
if(l==r)return mx[x]>m;
if(mx[ls]<=m)return getg(mid+1,r,rs,m);
else return at[x]-at[ls]+getg(l,mid,ls,m);
}
inline void pushup(int l,int r,int x){
at[x]=at[ls]+getg(mid+1,r,rs,mx[ls]);
mx[x]=max(mx[ls],mx[rs]);
}
inline void modify(int l,int r,int x,int pos,int h){
if(l==r){
mx[x]=double(h)/pos;
at[x]=1;
return;
}
if(pos<=mid)modify(l,mid,ls,pos,h);
else modify(mid+1,r,rs,pos,h);
pushup(l,r,x);
}
int main()
{
int in,im;
cin>>in>>im;
rep(v1,1,im){
int ix,iy;
scanf("%d %d",&ix,&iy);
modify(1,in,1,ix,iy);
printf("%d\n",at[1]);
}
return 0;
}

浙公网安备 33010602011771号