這裡放傳送門
可以發現如果一段樓房能被看見,那麼它們跟原點的連線的斜率都是單調遞增的。於是這就變成了乙個維護上公升序列的題。這裡的上公升序列不是最長上公升子串行那樣的東西,而是相當於貪心地選擇,選中的子串行中的每乙個元素它前面都不能存在大於等於它的元素。比如說,有乙個斜率序列是1,2,4,3,4,如果是做最長上公升子串行那種東西的話我們會選擇[1,2,3,4]這個子串行,但這道題裡面不行,因為第四個位置的3被第三個位置的4給擋住了,就看不見了,所以只能選到[1,2,4]這個子串行。
那麼問題就變成了動態修改某個點的值,求整個序列裡最長的這樣的上公升序列長度,可以用線段樹來維護。首先因為這個東西和區間最大值有關,如果某個區間的最大值比較大的話它可能會擋住後面所有的區間,後面的區間就不會有貢獻了。所以維護乙個max表示區間最大值。然後再維護乙個val表示當前區間裡最長的上公升序列的長度。注意這裡是僅僅考慮當前區間不考慮左右兩邊是什麼情況,這樣才好用線段樹遞迴更新求解。
呼叫乙個類似二分的divide過程,帶入乙個比較引數v表示這段區間裡要查詢的,最小值必須大於v的一段上公升序列的長度。在update的時候要對右子樹節點呼叫這個過程,帶入的比較引數就是左子樹的最大值。顯然右子樹能看見的那一段上公升序列的開頭必須至少要大於左子樹最大值對吧。然後就進去查詢。注意這個divide過程的用途就是查詢在當前區間裡所有大於等於v的數字中的最長上公升序列。那麼如果當前區間的最大值直接就小於等於v就直接返回0,如果當前區間只有乙個數字的話就把它跟v比較一下返回結果。否則還是要把這個區間分成左右子樹的兩個部分來討論。如果左子樹的最大值小於等於v,那麼顯然答案只會有右子樹的那一部分,就對右子樹接著呼叫divide即可;否則可以看出右子樹那一部分會全部被看見,只需要對左子樹呼叫divide即可。而這裡最重要的乙個問題就是計算的「右子樹那一部分」並不是val[(i<<1)+1],同樣是因為右子樹那塊沒考慮到左子樹的影響,這作為乙個整體的時候應該是要考慮左子樹的。因為要返回的是這段區間的上公升序列長度,所以右子樹給的貢獻應該是總的減去左子樹的。這跟val[(i<<1)+1]不一樣,不一樣,真的不一樣。。。。。
#include
#include
#include
using
namespace
std;
int n,m,val[500010];
double max[500010];
bool dlt[500010];
int divide(int i,int l,int r,double v)
void change(int i,int l,int r,int x,double v)
int mid=(l+r)>>1;
if (x<=mid) change(i<<1,l,mid,x,v);
else change((i<<1)+1,mid+1,r,x,v);
max[i]=max(max[i<<1],max[(i<<1)+1]);
val[i]=val[i<<1]+divide((i<<1)+1,mid+1,r,max[i<<1]);
}int main()
return
0;}
這題最難繞的就是「左子樹+右子樹」並不等於總的,因為它update的時候右子樹是特殊計算的。還是因為在這個最長上公升序列的問題中雖然計算單個區間的時候是不考慮左右影響的,但是update的時候為了正確得到這個區間的結果,右子樹還是要考慮左子樹的影響的,這是造成val[i<<1]+val[(i<<1)+1]
≠ val[i]的原因。
BZOJ2957 樓房重建 線段樹
題目 time limit 10 sec memory limit 256 mb submit 2259 solved 1069 submit status discuss 小a的樓房外有一大片施工工地,工地上有n棟待建的樓房。每天,這片工地上的房子拆了又建 建了又拆。他經常無聊地看著窗外發呆,數自...
bzoj2957樓房重建 線段樹
小a的樓房外有一大片施工工地,工地上有n棟待建的樓房。每天,這片工地上的房子拆了又建 建了又拆。他經常無聊地看著窗外發呆,數自己能夠看到多少棟房子。為了簡化問題,我們考慮這些事件發生在乙個二維平面上。小a在平面上 0,0 點的位置,第i棟樓房可以用一條連線 i,0 和 i,hi 的線段表示,其中hi...
線段樹 bzoj2957 樓房重建
大概意思就是求序列從一位置開始的動態上公升序列。分塊可過,但這一類題目其實可用線段樹。也就是維護每個區間的上公升序列長度。下面這種求法只是用於當前節點所覆蓋的區間完全被查詢區間覆蓋。具體而言,查詢時如果左兒子的max值 k,只去右兒子找。如果max lc k,那麼右兒子的長度全部符合,只要再遞迴著找...