P4198 楼房重建

传送门

很妙的思路

首先,我们可以把每一栋楼房转化为它的顶部到原点这条直线的斜率,这样就变成了从一个序列中选出一个最长上升子序列(其实不是最长上升子序列,不过可以这么理解)

考虑用线段树来维护,对于每个区间,我们维护这个区间的最大值以及这个区间的答案,那么最后的答案就是\(ans[1]\)

对于叶节点来说,最大值就是它自己,答案为\(1\)

考虑怎么合并区间。

首先左边区间能看到的答案,当前区间必然也能看到

考虑右边的区间。如果右区间的最大值小于等于左区间的最大值,那么一定会被挡住啥都看不见

如果右区间的左区间的最大值小于等于左区间的最大值,左区间肯定会被挡住啥都看不见,那么我们就递归进右区间的右区间继续找答案

如果右区间的左区间的最大值大于左区间的最大值,那么所有右区间的右区间里所有原来能看到的现在还是能看到。而左区间里继续递归找

有点绕,建议看代码理解比较好

总的复杂度为\(O(nlog^2n)\)

//minamoto
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
int read(){
    int res,f=1;char ch;
    while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
    for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
    return res*f;
}
char sr[1<<21],z[20];int C=-1,Z=0;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
void print(int x){
    if(C>1<<20)Ot();if(x<0)sr[++C]='-',x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e5+5;
int n,m,ans[N<<2];double mx[N<<2];
#define ls (p<<1)
#define rs (p<<1|1)
int query(int p,int l,int r,double sl){
	if(mx[p]<=sl)return 0;if(l==r)return mx[p]>sl;
	int mid=(l+r)>>1;
	if(mx[ls]<=sl)return query(rs,mid+1,r,sl);
	return query(ls,l,mid,sl)+ans[p]-ans[ls];
}
void upd(int p,int l,int r,int x,double sl){
	if(l==r)return (void)(ans[p]=1,mx[p]=sl);
	int mid=(l+r)>>1;
	x<=mid?upd(ls,l,mid,x,sl):upd(rs,mid+1,r,x,sl);
	mx[p]=max(mx[ls],mx[rs]);
	ans[p]=ans[ls]+query(rs,mid+1,r,mx[ls]);
}
int main(){
//	freopen("testdata.in","r",stdin);
	n=read(),m=read();
	while(m--){
		int x=read(),y=read();
		upd(1,1,n,x,1.0*y/x);
		print(ans[1]);
	}
	return Ot(),0;
}
posted @ 2018-11-14 12:56  bztMinamoto  阅读(158)  评论(0编辑  收藏  举报
Live2D