小A在平面上(0,0)点的位置,第i栋楼房可以用一条连接(i,0)和(i,Hi)的线段表示,其中Hi为第i栋楼房的高度。如果这栋楼房上任何一个高度大于0的点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。
施工队的建造总共进行了M天。初始时,所有楼房都还没有开始建造,它们的高度均为0。在第i天,建筑队将会将横坐标为Xi的房屋的高度变为Yi(高度可以比原来大,也可以比原来小,甚至可以保持不变—建筑队这天什么事也没做)。请你帮小A数数每天在建筑队完工之后,他能看到多少栋楼房?
1<=Xi<=N,1<=Yi<=10^9,N,M<=100000
转成斜率之后就是求前缀最大值的个数(连续上升子序列长度)
单点修改,求值与前面的区间有关,考虑线段树。
如果记录区间最大值mx和前缀最大值的个数len,考虑如何合并两个区间:
就是左儿子的长度+在前面的最大值为mx[左儿子]的情况下右儿子的前缀最大值个数。
后面的东西相当于询问一个query(l,r,k),在前面最大值为k的情况下[l,r]的前缀最大值个数。
如果k>mx[l,mid],则答案为query(mid+1,r,k)
否则答案为query(l,mid,k)+query(mid+1,r,mx[左儿子]),而这个式子的右半部分与k无关,可以记录,实际上就是len[当前节点]-len[左儿子]。
一次修改之后需要重新维护log个节点,而维护一个节点相当于一次query,所以复杂度是nlog2n的。
答案就为len[1]
PS:此题可以加强为区间询问[l,r]能看到多少栋楼房,跨过mid时先求左半边的答案,返回长度和最大值,再讲最大值带入右半区间求解,当线段树区间被询问区间完全覆盖时,与上面的query一样用log的时间求解,所以复杂度仍然是log2的。
Code:
#include
#define maxn 100005
using namespace std;
int n,m,len[maxn<<2];
double mx[maxn<<2];
int query(int i,int l,int r,double k){
if(mx[i]<=k) return 0;
if(l==r) return 1;
int mid=(l+r)>>1;
if(k>=mx[i<<1]) return query(i<<1|1,mid+1,r,k);
else return query(i<<1,l,mid,k)+len[i]-len[i<<1];
}
void insert(int i,int l,int r,int x,double k){
if(l==r) {
len[i]=1,mx[i]=k;return;}
int mid=(l+r)>>1;
if(x<=mid) insert(i<<1,l,mid,x,k);
else insert(i<<1|1,mid+1,r,x,k);
mx[i]=max(mx[i<<1],mx[i<<1|1]);
len[i]=len[i<<1]+query(i<<1|1,mid+1,r,mx[i<<1]);
}
int main()
{
scanf("%d%d",&n,&m);
int x,y;
while(m--){
scanf("%d%d",&x,&y);
insert(1,1,n,x,1.0*y/x);
printf("%d\n",len[1]);
}
}