Description
小A的楼房外有一大片施工工地,工地上有N栋待建的楼房。每天,这片工地上的房子拆了又建、建了又拆。他经常无聊地看着窗外发呆,数自己能够看到多少栋房子。
为了简化问题,我们考虑这些事件发生在一个二维平面上。小A在平面上(0,0)点的位置,第i栋楼房可以用一条连接(i,0)和(i,Hi)的线段表示,其中Hi为第i栋楼房的高度。如果这栋楼房上任何一个高度大于0的点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。
施工队的建造总共进行了M天。初始时,所有楼房都还没有开始建造,它们的高度均为0。在第i天,建筑队将会将横坐标为Xi的房屋的高度变为Yi(高度可以比原来大——修建,也可以比原来小——拆除,甚至可以保持不变——建筑队这天什么事也没做)。请你帮小A数数每天在建筑队完工之后,他能看到多少栋楼房?
Input
第一行两个正整数N,M
接下来M行,每行两个正整数Xi,Yi
Output
M行,第i行一个整数表示第i天过后小A能看到的楼房有多少栋
Sample Input
3 4
2 4
3 6
1 1000000000
1 1
Sample Output
1
1
1
2
Hint
对于所有的数据1≤Xi≤N,1≤Yi≤109,N,M≤100000
Solution
注意到我们可以处理出楼房顶部和原点连线的斜率,这样一栋楼房能被看到当且仅当它左边楼房的斜率严格小于它的斜率。
用ans[i]表示线段树上节点i所表示的区间,从左往右能看到多少栋楼。
注意到ans[i]一定包含ans[lc],但不一定包含ans[rc]。(lc为左儿子,rc为右孩子)
我们记录max[i]为当前区间最大斜率,则如果max[lc]>=max[rc],则ans[rc]对当前区间肯定没有贡献(完全被挡住)。否则我们应该计算右儿子中大于max[lc]的数的个数。将右儿子再次分为左右两段,记为rc->lc和rc->rc,发现如果max[rc->lc]<=max[lc],那么左边完全没有贡献,我们递归计算rc->rc,否则rc->rc的贡献为ans[rc]-ans[rc->lc](注意不是ans[rc->rc]),递归计算rc->lc。
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
template<typename T>inline void read(T &x){
T f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(x=0;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
x*=f;
}
const int maxn=100010;
struct Segment_Tree{
#define lc x<<1
#define rc x<<1|1
double mx[maxn<<2];
int ans[maxn<<2],L[maxn<<2],R[maxn<<2];
void Build(int x,int l,int r){
if((L[x]=l)==(R[x]=r))return;
int mid=(l+r)>>1;
Build(lc,l,mid);Build(rc,mid+1,r);
}
int Count(int x,double M){ //计算区间x内有多少个大于M的数
if(L[x]==R[x])return M<mx[x];
int mid=(L[x]+R[x])>>1;
if(M>=mx[lc])return Count(rc,M);
return Count(lc,M)+ans[x]-ans[lc];
}
void Change(int x,int pos,double k){
if(L[x]==R[x])return mx[x]=k,ans[x]=1,void();
int mid=(L[x]+R[x])>>1;
if(pos<=mid)Change(lc,pos,k);
else Change(rc,pos,k);
mx[x]=max(mx[lc],mx[rc]);
ans[x]=ans[lc]+Count(rc,mx[lc]);
}
}tree;
int n,m;
int main(){
read(n);read(m);
tree.Build(1,1,n);
while(m--){
double x,y;
read(x);read(y);
tree.Change(1,x,y/x);
printf("%d\n",tree.ans[1]);
}
return 0;
}