【題解】樓房重建
阿新 • • 發佈:2021-06-25
\(\text{Solution:}\)
分析題目,我們看不到一個房子,當且僅當它的斜率嚴格不大於前面的房子斜率。
題目讓我們求的就是:強制選擇出嚴格單調遞增的序列長度最大值,全域性詢問,單點修改。
看著很線段樹,但是區間的資訊怎麼去合併呢?
開始的思路:首先長度必須要維護,然後維護一個最大值,這樣合併的時候如果右邊最大值小於等於左邊的就可以直接跳過了。但是剩下的部分怎麼維護?
忽略掉了線段樹的本質:分治。
我們可以將右區間劃分為兩個區間來降低問題複雜度。它被我們分成左右兩個區間後:
若被劃分左區間的最大值要大於當前需要大於的值,我們就去遞迴左區間,拼接上右邊的答案:\(len[x]-len[ls[x]].\)
那麼如果被劃分的左區間最大值小於等於當前需要大於的值,我們就跳過左區間直接去右區間。
這樣,我們通過分治成功將問題規模縮小一半,進行一次更新的複雜度是 \(O(\log n).\)
於是總複雜度就是:\(O(m\log^2 n).\)
關於精度問題:我用 \(\text{long double}\) 才卡過去。不知道有沒有什麼好方法,歡迎指教。
有很多細節:比如 pushup 函式實現的時候,需要大於的那個值是不需要改變這個引數的。每次我們都進入一個新區間去計算它,但需要大於的值沒有變。
建樹的時候由於沒有任何一座樓房,所以 len 應該初始成 \(0.\)
#include<bits/stdc++.h> using namespace std; const int MAXN=4e5+10; int ls[MAXN],rs[MAXN],tot,n,m; int L[MAXN],R[MAXN],rt,len[MAXN]; long double maxn[MAXN]; const long double eps=1e-12; inline long double Max(long double x,long double y){return x-y>eps?x:y;} inline void pushupmax(int x){maxn[x]=Max(maxn[ls[x]],maxn[rs[x]]);} inline int pushup(long double v,int x){ if(maxn[x]<=v)return 0; if(L[x]==R[x]){ if(maxn[x]>v)return 1; } int s1=ls[x],s2=rs[x]; if(maxn[s1]<=v)return pushup(v,s2); else{ return pushup(v,s1)+len[x]-len[s1]; } } void build(int &x,int l,int r){ x=++tot; L[x]=l;R[x]=r; if(l==r){ maxn[x]=0; len[x]=0; return; } int mid=(l+r)>>1; build(ls[x],l,mid); build(rs[x],mid+1,r); pushupmax(x); len[x]=len[ls[x]]+pushup(maxn[ls[x]],rs[x]); } void change(int x,int l,int r,long double v){ if(L[x]==R[x]){ maxn[x]=v; len[x]=1; return; } int mid=(L[x]+R[x])>>1; if(l<=mid)change(ls[x],l,r,v); if(mid<r)change(rs[x],l,r,v); pushupmax(x); len[x]=len[ls[x]]+pushup(maxn[ls[x]],rs[x]); } int main(){ //freopen("P4198_5.in","r",stdin); //freopen("My.out","w",stdout); scanf("%d%d",&n,&m); build(rt,1,n); for(int i=1;i<=m;++i){ int x,y; scanf("%d%d",&x,&y); change(rt,x,x,(long double)(1.0*y/x)); printf("%d\n",len[rt]); } return 0; }