POJ 3667 Hotel 線段樹 區間合併

2022-03-10 22:13:49 字數 3744 閱讀 9039

**自:

【題目鏈結】hotel

【題目型別】

線段樹 區間合併

&題意:

有乙個線段,從1到n,下面m個操作,操作分兩個型別,以1開頭的是查詢操作,以2開頭的是更新操作

1 w 表示在總區間內查詢乙個長度為w的可用區間,並且要最靠左,能找到的話返回這個區間的左端點並占用了這個區間,找不到返回0

好像n=10 , 1 3 查到的最左的長度為3的可用區間就是[1,3],返回1,並且該區間被占用了

2 a len , 表示從單位a開始,清除一段長度為len的區間(將其變為可用,不被占用),不需要輸出

因此看sample的話就可以理解了

&題解:

記錄一下自己的感悟:

用線段樹,首先要定義好線段樹的節點資訊,一般看到乙個問題,很難很快能確定線段樹要記錄的資訊

做線段樹不能為了做題而做,首先線段樹是一種輔助結構,它是為問題而生的,因而必須具體問題具體分析

回憶一下rmq問題,其實解決rmq有很多方法,根本不需要用到線段樹,用線段樹解決rmq,其實是利用線段樹的性質來輔助解決這個問題

回憶一下求矩形面積並或周長並的問題,一般使用的是掃瞄線法,其實掃瞄線法和線段樹一點關係都沒有,掃瞄線法應該歸為計算幾何的演算法,

使用線段樹只是為了輔助實現掃瞄線法

因而回到這題,要解,必須分析問題本質,才去思考怎麼用線段樹來輔助,另外為什麼能用線段樹輔助是可行的,這個問題似乎更有價值

1 查詢操作,找一段長度為w的沒被覆蓋的最左的區間

2 更新操作,將某段連續的區域清空

更新操作相對容易解決,關鍵是怎麼實現查詢操作

既然是要找一段長度至少為w的區間,要做到這點,其實不難,我們可以在每個線段樹的節點裡增加乙個域tlen,表示該區間可用的區間的最大長度,

至於這個tlen區間的具體位置在**不知道,只是知道該區間內存在這麼一段可用的區間,並且注意,這個tlen表示的是最大長度,該節點可能有多段可用的區間,但是最長的長度是tlen

記錄了這個資訊,至少能解決乙個問題,就是能不能找到乙個合適的區間。如果查詢的區間長度w > 總區間的tlen,那麼查詢一定是失敗的(總區間中可以的最大區間都不能滿足那就肯定失敗)

但這遠遠不夠,其一查詢是要返回區間的具體位置的,這裡無法返回位置,另外是要查詢最左區間,最左的且滿足》=w的區間可能不是這個tlen區間

那麼我們進一步思考這個問題

首先我們先增加兩個域,llen,rlen

llen表示乙個區間從最左端開始可用的且連續的最大長度

例如區間[1,5],覆蓋情況為[0,0,0,1,1],llen = 3,從最左端有3格可以利用

區間[1,5],覆蓋情況為[1,0,0,0,0],llen = 0,因為從最左端開始找不到1格可用的區間

rlen表示乙個區間從最右端開始可用的且連續的最大長度

例如區間[1,5],覆蓋情況為[1,0,1,0,0],rlen = 2,從最右端有2格可以利用

區間[1,5],覆蓋情況為[0,0,0,0,1],rlen = 0,因為從最右端開始找不到1格可用的區間

對於乙個區間,我們知道它左半區間的tlen,和右半區間的tlen,如果左半區間的tlen >= w ,那麼我們一定能在左邊找到(滿足最左),所以可以深入到左半區間去確定該區間的具體位置

如果左端的不滿足,那麼我們要先考慮橫跨兩邊的區間(因為要滿足最左),因而記錄的llen,rlen可以派上用場,一段橫跨的區間,

那麼是 左邊區間rrlen + 右邊區間llen ,如果滿足的話,就是該區間了,它的位置也是可以確定的

如果橫跨的區間不滿足,那麼就在右半區間找,如果右半區間的tlen >= w , 那麼可以在右半區間找到,所以深入到右半區間去確定它的具體位置,否則的話,整個查詢就失敗了

可見查詢是建立在tlen,llen,rlen這個資訊之上的,而每次查詢後其實伴隨著修改,而且還有專門的修改操作,這些修改操作都會改變tlen,llen,rlen的值,所以在更新的時候是時刻維護這些資訊

關於這3個資訊的維護

當前區間的tlen = max (這個不難理解吧,取左右較大的那個,或者橫跨中間的那個)

如果左半區間全部可以用: 當前區間llen = 左半區間llen(tlen) + 右半區間llen

左半區間部分能用: 當前區間llen = 左半區間llen

如果右半區間全部能用: 當前區間rlen = 右半區間rlen(tlen) + 左半區間rlen

右半區間部分能用: 當前區間rlen = 右半區間rlen

這樣就全部維護好了

&**:

#include #include #define lch(i) ((i)<<1)

#define rch(i) ((i)<<1|1)

#define max(a,b) ((a)>(b)?(a):(b))

#define min(a,b) ((a)<(b)?(a):(b))

#define n 50010

#define inf 0x3f3f3f3f

struct node

int cal_len()

void updata_len()

}t[4*n];

void build(int l ,int r ,int rt)

int query(int w ,int rt)

if(t[lch(rt)].tlen >= w) //左孩子的可用區間可以滿足,那麼一定在左孩子區間內

return query(w , lch(rt));

else if(t[lch(rt)].rlen + t[rch(rt)].llen >= w) //橫跨左右孩子且連續的區間可以滿足,那麼可以直接返回下標

return ( t[lch(rt)].r - t[lch(rt)].rlen + 1 );

else if(t[rch(rt)].tlen >= w) //右孩子的可用區間可以滿足,那麼去右孩子處找

return query(w , rch(rt));

else //找不到可用的區間

return 0;

}void updata(int l ,int r ,int val ,int rt)

if(t[rt].mark != -1) //延遲標記,父親資訊傳遞給兒子

int mid = t[rt].mid();

if(l > mid) //修改的區間在右孩子

updata(l , r , val , rch(rt));

else if(r <= mid) //修改的區間在左孩子

updata(l , r , val , lch(rt));

else

int tmp = max(t[lch(rt)].tlen , t[rch(rt)].tlen);

t[rt].tlen = max(tmp , t[lch(rt)].rlen + t[rch(rt)].llen);

t[rt].llen = t[lch(rt)].llen;

t[rt].rlen = t[rch(rt)].rlen;

if(t[lch(rt)].tlen == t[lch(rt)].cal_len() )

t[rt].llen += t[rch(rt)].llen;

if(t[rch(rt)].tlen == t[rch(rt)].cal_len() )

t[rt].rlen += t[lch(rt)].rlen;

return ;

}int main()

else

}return 0;

}

POJ 3667 Hotel 線段樹區間合併

這道題應該算是比較經典的線段樹了 題意是 這座巨型賓館在一條超長走廊上有n 1 n 50000 個排成一排的房間,每個房間都能欣賞到蘇必利爾湖的好景色。現在所有的房間都是空的。現在bessie等旅客們正在不斷地發出訂房和退房要求。你需要接受m 1 m 50000 條指令 每條指令的第乙個數字為1或2...

poj3667 Hotel 線段樹 區間合併

題意 有乙個旅館初始有n個空位置,操作一找連續空位置長度為l的最左端,如果不存在輸0。操作二從 a,a b 1 區間的位置重新 置為空。思路 線段樹區間合併。線段樹維護四個資訊 1 區間連續為空地最長長度 2 從區間左端連續空地最長長度 3 從區間右端向左 連續空的最長長度 4 區間的懶惰標記 pu...

POJ 3667 hotel 線段樹區間合併

有n房間,序號1 n,m次詢問,詢問 1 b 代表入住b人,你需要找一段連續的6間房間,若有返回序號最小的房間序號,若沒有,返回0 詢問 2 b c 代表清理房間b c,清理之後b c房間可以入住。思路 記錄某區間的最大連續房間個數 區間從左邊第乙個開始的連續空房間個數,右邊最後乙個往前連續的空房間...