1. 程式人生 > >[Luogu P4198]樓房重建(線段樹)

[Luogu P4198]樓房重建(線段樹)

ace range lse esp find 線段相交 names 復制 code

題目描述

小A的樓房外有一大片施工工地,工地上有N棟待建的樓房。每天,這片工地上的房子拆了又建、建了又拆。他經常無聊地看著窗外發呆,數自己能夠看到多少棟房子。

為了簡化問題,我們考慮這些事件發生在一個二維平面上。小A在平面上(0,0)點的位置,第i棟樓房可以用一條連接(i,0)和(i,Hi)的線段 表示,其中Hi為第i棟樓房的高度。如果這棟樓房上任何一個高度大於0的點與(0,0)的連線沒有與之前的線段相交,那麽這棟樓房就被認為是可見的。

施工隊的建造總共進行了M天。初始時,所有樓房都還沒有開始建造,它們的高度均為0。在第i天,建築隊將會將橫坐標為Xi的房屋的高度變為Yi(高 度可以比原來大—修建,也可以比原來小—拆除,甚至可以保持不變—建築隊這天什麽事也沒做)。請你幫小A數數每天在建築隊完工之後,他能看到多少棟樓房?

輸入輸出格式

輸入格式:

第一行兩個正整數N,M

接下來M行,每行兩個正整數Xi,Yi

輸出格式:

M行,第i行一個整數表示第i天過後小A能看到的樓房有多少棟

輸入輸出樣例

輸入樣例#1: 復制
3 4
2 4
3 6
1 1000000000
1 1
輸出樣例#1: 復制
1
1
1
2

說明

對於所有的數據1<=Xi<=N,1<=Yi<=10^9

N,M<=100000

線段樹博大精深。

y/x作為每個點的值,求最靠左的極長上升子序列,線段樹維護。

考慮如何合並兩個區間,設c[x]為x區間內的子序列長度。顯然是左邊的極長上升子序列選完,然後右邊選出第一個數大於max{左邊所有數(也就是左邊子序列末尾)}的極長上升子序列。

至於怎麽求一個區間內第一個數大於某個值的子序列長度,再用一個函數解決,que(x,k)表示x節點的區間內大於k的極長上升子序列的長度,遞歸查詢即可。

實現起來會發現是c[]和find()的相互利用,比較巧妙。

因為修改會帶上查詢,所以復雜度$O(n\log^2 n)$

另外這題數據類型是實數,會發現實數比較的時候如果完全不考慮精度問題(直接用>=之類的運算)的話不存在問題,而如果考慮了之後eps設寬了反而拿不到分,eps設到1e-10才能拿到分。

仿佛又看到了NOIP2017D2T1的慘敗。

 1 #include<cmath>
 2 #include<cstdio>
 3
#include<algorithm> 4 #define ls (x<<1) 5 #define rs (ls|1) 6 using namespace std; 7 8 const int N=100100; const double eps=1e-10; 9 int n,m,x,c[N<<2]; double y,mx[N<<2]; 10 11 int que(int x,int L,int R,double k){ 12 if (k-mx[x]>-eps) return 0; 13 if (L==R) return c[x]; 14 int mid=(L+R)>>1; 15 if (k-mx[ls]>-eps) return que(rs,mid+1,R,k); 16 else return que(ls,L,mid,k)+c[x]-c[ls]; 17 } 18 19 void mdf(int x,int L,int R,int pos,double k){ 20 if (L==R){ mx[x]=k; if (fabs(k)<eps) c[x]=0; else c[x]=1; return; } 21 int mid=(L+R)>>1; 22 if (pos<=mid) mdf(ls,L,mid,pos,k); else mdf(rs,mid+1,R,pos,k); 23 mx[x]=max(mx[ls],mx[rs]); c[x]=c[ls]+que(rs,mid+1,R,mx[ls]); 24 } 25 26 int main(){ 27 scanf("%d%d",&n,&m); 28 for (int i=1; i<=m; i++) scanf("%d%lf",&x,&y),mdf(1,1,n,x,y/x),printf("%d\n",c[1]); 29 return 0; 30 }

[Luogu P4198]樓房重建(線段樹)