推薦技術公眾號:不愛睡覺的大豬
線段樹
題意:有一個線段,從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{ 左半區間tlen , 右半區間tlen , 左半區間rlen+右半區間llen} (這個不難理解吧,取左右較大的那個,或者橫跨中間的那個)
如果左半區間全部可以用: 當前區間llen = 左半區間llen(tlen) + 右半區間llen
左半區間部分能用: 當前區間llen = 左半區間llen
如果右半區間全部能用: 當前區間rlen = 右半區間rlen(tlen) + 左半區間rlen
右半區間部分能用: 當前區間rlen = 右半區間rlen
這樣就全部維護好了
代碼學習了小HH的代碼風格
#include <cstdio> #include <cstring> #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 l,r; int mark; int tlen,llen,rlen; int mid(){ return (l+r)>>1; } int cal_len(){ return r-l+1; } void updata_len(){ tlen = llen = rlen = ( mark ? 0 : cal_len() ); } }t[4*N]; void build(int l ,int r ,int rt) { t[rt].l = l; t[rt].r = r; t[rt].tlen = t[rt].llen = t[rt].rlen = t[rt].cal_len(); t[rt].mark = 0; if(l == r) return ; int mid = t[rt].mid(); build(l , mid , lch(rt)); build(mid+1 , r , rch(rt)); return ; } int query(int w ,int rt) { if(t[rt].l == t[rt].r && w == 1) //葉子特判 return t[rt].l; if(t[rt].mark != -1) //延遲標記,父親信息傳遞給兒子 { t[lch(rt)].mark = t[rch(rt)].mark = t[rt].mark; t[rt].mark = -1; t[lch(rt)].updata_len(); //傳遞信息后更新孩子的區間覆蓋情況 t[rch(rt)].updata_len(); //傳遞信息后更新孩子的區間覆蓋情況 } 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].l == l && t[rt].r == r) { t[rt].mark = val; t[rt].updata_len(); return ; } if(t[rt].mark != -1) //延遲標記,父親信息傳遞給兒子 { t[lch(rt)].mark = t[rch(rt)].mark = t[rt].mark; t[rt].mark = -1; t[lch(rt)].updata_len(); //傳遞信息后更新孩子的區間覆蓋情況 t[rch(rt)].updata_len(); //傳遞信息后更新孩子的區間覆蓋情況 } 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 { updata(l , mid , val , lch(rt)); updata(mid+1 , r , val , rch(rt)); } 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() { int n,m; scanf("%d%d",&n,&m); build(1,n,1); while(m--) { int choose; scanf("%d",&choose); if(choose == 1) //查詢操作 { int w; scanf("%d",&w); int index = query(w,1); printf("%d\n",index); if(index) updata(index , index+w-1 , 1 , 1); } else { int l,len; scanf("%d%d",&l,&len); updata(l , l+len-1 , 0 , 1); } } return 0; }