簡介:
發現,有的時候,線段樹需要維護的區間很大很大,但是實際用到的節點很少很少。
那么,我們干脆就不要開這么多的節點,用到的時候再向內存要。
也就是說,我們建立了一棵殘疾的線段樹,缺少很多枝葉,但是絕對夠用了。
畫個圖大概理解一下(雖然也不太對)
實心邊框的點都是我們申請內存給的,虛的點是沒用的。就算申請也不用,實在是浪費資源。
所以,
我們開局只有一個根,
裝備全靠給。
枝葉全靠給。
例如我們要建立一個權值線段樹,但是在線操作不讓你離散化,值域又是inf級別的,
像這樣,即使這個區間的范圍很大,但是如果詢問q比較少的話,我們只需要qloginf個節點,就可以辦到。
具體代碼實現:
不同的操作,但是大同小異。
還是類似於一般線段樹的。
框架:
function(int &x,int l,int r,int blablabla){ if(!x){ //建新節點,並處理信息
if(l==r) //葉子節點由於是真正要用的節點(對於單點),往往還要特殊記錄信息
} if(blablabla) return ???//判斷是否能返回等等
(int ret) // 如果需要返回時停留更新信息,就弄一個ret
if(blablabla) return (t[x].ls,l,mid,blablabla) if(blablabla) return (t[x].rs,mid+1,r,blablabla); (pushup(x)) //回溯后更新
}
發現和主席樹有點像,但是省空間的思想還是有些不同的。
主席樹是:多棵線段樹,利用相鄰之間有很大部分是相同的。可以在之前線段樹基礎上建立線段樹。
特點:許多線段樹共用兒子節點
動態開點線段樹:一棵線段樹,利用實際用到的點不多,少開了很多節點。
特點:區間范圍很大(通常不能直接開下)
共同點:(都是線段樹)
都通過新加入的節點有限,進行的空間優化。使得時間空間復雜度都是logn/次
例題:(里面也有本篇的部分講解)
NOIP2017 列隊
upda:2018.9.22
主席樹相鄰的有很大的關系,那么動態開點線段樹呢?
動態開點線段樹也可以支持合並。
函數:
int merge(int x,int y,int l,int r){ if(!x||!y) return x|y; if(l==r){ do something on the leaf } else{ t[x].ls=merge(t[x].ls,t[y].ls,l,mid); t[x].rs=merge(t[x].rs,t[y].rs,mid+1,r); pushup(x); } return x; }
理解:空節點直接返回,然后本質其實是相當於利用y的兒子們,修改x兒子們的信息。
同樣也是會達到共用兒子的目的。
注意葉子節點的特判暴力合並。
其實類似左偏樹的合並。
但是復雜度的原理證明不太相同。
這個merge的證明主要是通過,每成功合並一次,節點數會少1個。
如果開始有mlogn個節點,那么最多合並mlogn次,復雜度就可以保證。
空間復雜度:總共nlogn,也就是開始節點個數。
發現,其實我們把動態開點線段樹和主席樹聯系起來了!
但是,主席樹不能直接合並多棵(其實也可以,但就和動態開點線段樹沒區別了。),必須在一棵基礎上建立,而且不能靈活支持修改。
動態開點線段樹就可以支持一次次地合並多棵為一棵,但是同樣,合並后,再修改就比較麻煩了。
例題:
[Vani有約會]雨天的尾巴——樹上差分+動態開點線段樹合並
upda:2018.11.1
根據上面兩個題的做法,
動態開點線段樹,還經常開很多棵,
對於某個點要打很多不同的標記,並且父子之間標記要快速合並的話,
那么每個點動態開點線段樹就比較優秀了。
(相較於一般的差分只是差分一種標記,這個就比較強大了。)
甚至:
天天愛跑步——樹上差分
這個題用個動態開點線段樹直接打差分標記也是可以的。。。(如果你不會全局桶的騷操作的話)