1. 程式人生 > 其它 >【題解】樓房重建

【題解】樓房重建

Problem

\(\text{Solution:}\)

分析題目,我們看不到一個房子,當且僅當它的斜率嚴格不大於前面的房子斜率。

題目讓我們求的就是:強制選擇出嚴格單調遞增的序列長度最大值,全域性詢問,單點修改。

看著很線段樹,但是區間的資訊怎麼去合併呢?

開始的思路:首先長度必須要維護,然後維護一個最大值,這樣合併的時候如果右邊最大值小於等於左邊的就可以直接跳過了。但是剩下的部分怎麼維護?

忽略掉了線段樹的本質:分治。

我們可以將右區間劃分為兩個區間來降低問題複雜度。它被我們分成左右兩個區間後:

若被劃分左區間的最大值要大於當前需要大於的值,我們就去遞迴左區間,拼接上右邊的答案:\(len[x]-len[ls[x]].\)

注意不是 \(len[rs[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;
}