题解:P4198 楼房重建

link

考虑将每个点存为连接 \((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;
}
posted @ 2025-07-12 18:11  FugiPig  阅读(18)  评论(0)    收藏  举报