problem
在一个二维平面上有 n n n 栋楼房,第 i i i 栋楼房可以用以 ( i , 0 ) (i,0) (i,0) 和 ( i , h i ) (i,h_i) (i,hi) 为端点的线段表示,一开始所有的 h i = 0 h_i=0 hi=0。
有 m m m 次操作,每次可以修改一个 h i h_i hi,并且在每次修改之后查询在 ( 0 , 0 ) (0,0) (0,0) 能看到的楼房个数(如果以 ( i , h i ) (i,h_i) (i,hi) 和 ( 0 , 0 ) (0,0) (0,0) 为端点的线段与其它楼房没有交点,那么 i i i 就可见)。
数据范围: n , m ≤ 1 0 5 n,m\le10^5 n,m≤105。
solution
可以先算一下 ( i , h i ) (i,h_i) (i,hi) 与 ( 0 , 0 ) (0,0) (0,0) 的直线的斜率,能看到楼房的斜率一定是递增的。
考虑用线段树来维护这个东西,那么关键就在于 pushup 操作了。
首先,左儿子的答案是可以直接加进来的,而右儿子会受到左儿子中最大值的限制。
具体来说,我们可以找到右儿子中第一个比左儿子的 m x mx mx 大的位置,它的左边都不能选,右边按照原来的计算即可。
一边找一边算答案即可。
由于 pushup 是 O ( log n ) O(\log n) O(logn) 的,所以总时间复杂度 O ( n log 2 n ) O(n\log^2n) O(nlog2n)。
code
#include<bits/stdc++.h>
using namespace std;
namespace IO{
const int Rlen=1<<22|1;
char buf[Rlen],*p1,*p2;
inline char gc(){
return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
}
template<typename T>
inline T Read(){
char c=gc();T x=0,f=1;
while(!isdigit(c)) f^=(c=='-'),c=gc();
while( isdigit(c)) x=(x+(x<<2)<<1)+(c^48),c=gc();
return f?x:-x;
}
inline int in() {return Read<int>();}
}
using IO::in;
const int N=1e5+5;
int n,m,sum[N<<2];
double mx[N<<2];
#define mid ((l+r)>>1)
int find(double num,int root,int l,int r){
if(l==r) return mx[root]>num;
if(mx[root<<1]>num) return find(num,root<<1,l,mid)+sum[root]-sum[root<<1]; //注意这里不是sum[root<<1|1]。
return find(num,root<<1|1,mid+1,r);
}
void pushup(int root,int l,int r){
sum[root]=sum[root<<1];
mx[root]=max(mx[root<<1],mx[root<<1|1]);
if(mx[root<<1]>mx[root<<1|1]) return;
sum[root]+=find(mx[root<<1],root<<1|1,mid+1,r);
}
void Modify(int root,int l,int r,int pos,double k){
if(l==r){
mx[root]=k,sum[root]=1;
return;
}
if(pos<=mid) Modify(root<<1,l,mid,pos,k);
else Modify(root<<1|1,mid+1,r,pos,k);
pushup(root,l,r);
}
#undef mid
int main(){
n=in(),m=in();
while(m--){
int x=in(),y=in();
Modify(1,1,n,x,1.0*y/x),printf("%d\n",sum[1]);
}
return 0;
}