题目
这些事件发生在一个二维平面上,待建的楼房的横坐标在1到N(1<=N<=1e5)之间。
小A在平面上(0,0)点的位置,第i栋楼房可以用一条连接(i,0)和(i,Hi)的线段表示,其中Hi为第i栋楼房的高度。
如果这栋楼房上任何一个高度大于0的点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。
施工队的建造总共进行了M(1<=M<=1e5)天。初始时,所有楼房都还没有开始建造,它们的高度均为0。在第i天,建筑队将会将横坐标为Xi(1<=Xi<=N)的房屋的高度变为Yi(1<=Yi<=1e9)(高度可以比原来大---修建,也可以比原来小---拆除,甚至可以保持不变---建筑队这天什么事也没做)。
请你帮小A数数每天在建筑队完工之后,他能看到多少栋楼房?
输出共m行,第i行一个整数,表示第i天过后小A能看到的楼房有多少栋
思路来源
https://www.cnblogs.com/ljh2000-jump/p/6038304.html 分块
http://hzwer.com/6746.html 线段树二分
题解
①分块,分成根号n块,每个块内维护一个顺序扫的严格上升子序列和块内最大值,
改的时候改当前块内的,查的时候对于每个块二分统计块内对答案贡献的个数
②线段树二分,每个区间p维护区间最值mx和只考虑这个区间时的答案ans,根区间的ans即为所求
修改时一并更新,cal(p,v)用于计算如果当前值是v,在p区间内v后面还能有几个数
一个显而易见的结论是,这种数字的值是单调递增的。
我们修改一个数只会对这个数后面的数造成影响。
考虑线段树划分出来的若干线段。这里有两种情况:
1、某个线段中的最大值小于等于修改的数,那么这个线段的贡献为0,无需处理
2、否则我们将这个线段分成两个并单独考虑,
如果左侧的最大值大于修改的数,那么是不影响右侧的贡献的,只需递归处理左侧;
否则就变成了第一种情况
复杂度O(n(logn)^2)
代码1(分块)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=1e5+10,M=400;
const double eps=1e-9;
struct node
{
int cnt;
double a[M],mx;
node():cnt(0),mx(0){
}
}e[M];
int n,m,x,y,blk,up,in[N],l[N],r[N],L,R,mid,ans;
double now[N],h;
int main()
{
scanf("%d%d",&n,&m);
blk=sqrt(n);
for(int i=0;i<n;++i)
{
in[i]=i/blk;
l[i]=in[i]*blk;
r[i]=min(n,l[i]+blk);
}
up=in[n-1];
while(m--)
{
scanf("%d%d",&x,&y);
x--;
now[x]=1.0*y/(x+1.0);
int &z=in[x];
e[z].cnt=e[z].mx=0;
for(int i=l[x];i<r[x];++i)
{
if(now[i]<=e[z].mx)continue;
e[z].mx=now[i];
e[z].a[++e[z].cnt]=e[z].mx;
}
h=0;ans=0;
for(int i=0;i<=up;++i)
{
if(!e[i].cnt||e[i].mx<=h)continue;
L=1,R=e[i].cnt;
while(L<=R)
{
mid=(L+R)>>1;
if(e[i].a[mid]>=h)R=mid-1;
else L=mid+1;
}
ans+=e[i].cnt-L+1;
h=e[i].a[e[i].cnt];
}
printf("%d\n",ans);
}
return 0;
}
代码2(线段树二分)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=1e5+10;
int n,m,x,y;
struct node
{
int ans,l,r;
double mx;
}e[N*4];
void build(int p,int l,int r)
{
e[p].l=l,e[p].r=r;
if(l==r)return;
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
int cal(int p,double v)//该区间内>v的值有多少个
{
int l=e[p].l,r=e[p].r;
if(l==r)return e[p].mx>v;
if(e[p<<1].mx<=v)return cal(p<<1|1,v);//右子树里找
return e[p].ans-e[p<<1].ans+cal(p<<1,v);//左+完整右子树
//此处的右子树 是需要考虑完整e[p]区间 左侧最大值<=右侧的所有ans的
//所以用e[p].ans-e[p<<1].ans 而不是e[p<<1|1].ans 毕竟二者不等价
}
void upd(int p,int pos,double v)
{
int l=e[p].l,r=e[p].r;
if(l==r)
{
e[p].ans=1;
e[p].mx=v;
return;
}
int mid=(l+r)>>1;
if(pos<=mid)upd(p<<1,pos,v);
else upd(p<<1|1,pos,v);
e[p].mx=max(e[p<<1].mx,e[p<<1|1].mx);
e[p].ans=e[p<<1].ans+cal(p<<1|1,e[p<<1].mx);
}
int main()
{
scanf("%d%d",&n,&m);
build(1,1,n);
while(m--)
{
scanf("%d%d",&x,&y);
upd(1,x,1.0*y/x);
printf("%d\n",e[1].ans);
}
return 0;
}