動態開點線段樹——節約資源,你值得擁有


簡介:

發現,有的時候,線段樹需要維護的區間很大很大,但是實際用到的節點很少很少。

那么,我們干脆就不要開這么多的節點,用到的時候再向內存要。

也就是說,我們建立了一棵殘疾的線段樹,缺少很多枝葉,但是絕對夠用了。

畫個圖大概理解一下(雖然也不太對)

實心邊框的點都是我們申請內存給的,虛的點是沒用的。就算申請也不用,實在是浪費資源。

所以,

我們開局只有一個根,

裝備全靠給。

枝葉全靠給。

 

例如我們要建立一個權值線段樹,但是在線操作不讓你離散化,值域又是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

根據上面兩個題的做法,

動態開點線段樹,還經常開很多棵,

對於某個點要打很多不同的標記,並且父子之間標記要快速合並的話,

那么每個點動態開點線段樹就比較優秀了。

(相較於一般的差分只是差分一種標記,這個就比較強大了。)

甚至:

 

天天愛跑步——樹上差分

這個題用個動態開點線段樹直接打差分標記也是可以的。。。(如果你不會全局桶的騷操作的話)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM