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
對於所有的數據
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;
}