淺談動態開點線段樹


淺談動態開點線段樹

本篇隨筆簡單講解一下線段樹的常見優化技巧——動態開點。

要學動態開點首先得會線段樹,如果不會的話,看官請走這邊——

簡單線段樹詳解

權值線段樹詳解

動態開點的概念和功能

現在要讓你維護一棵值域為\(10^9\)的權值線段樹。

掐指一算,按線段樹開的話,四倍空間是\(4\times10^9\),空間必炸。

於是你開始懷疑這道題有問題,大罵出題人毒瘤

其實你只是不會動態開點。

所謂動態開點,就是用什么開什么。簡單的說,就是建立一棵“殘疾”的線段樹,上面只有詢問過的相關節點。從而節約了大量空間,讓上面的\(10^9\)權值線段樹成為可行。

其實看這個“動態開點”的名字就能隱約猜到這個概念和功能。

這樣的話,我們最后的空間復雜度只與詢問次數有關,也就是\(O(q\log N)\)

動態開點的代碼實現

先放代碼,針對代碼進行解釋。

struct segment_tree
{
    int sum,lson,rson;
}tree[maxq<<2];
int tot;
void update(int &pos,int l,int r,int k)
{
    int mid=(l+r)>>1;
    if(!pos)
        pos=++tot;
   	if(l==r)
    {
        tree[pos].sum+=k;
        return;
    }
    if(x<=mid)
        update(tree[pos].lson,l,mid,k);
    if(y>mid)
        update(tree[pos].rson,mid+1,r,k);
    tree[pos].sum=tree[tree[pos].lson].sum+tree[tree[pos].rson].sum;
}
int main()
{
    int root=0;
    update(root,1,maxn,k);
    return 0;
}

大家會發現,這份代碼較之原版的簡單線段樹並沒有多大的差別,其中的精妙就在於節點編號的命名上

原版線段樹是一棵完整的二叉樹,所以我們采取計算的方式來算出每個節點的左右兒子的編號,具體方式就是(這是條規律):左節點:當前節點×2,右節點:當前節點×2+1。

但是我們動態開點就不一樣了。因為是一棵殘疾的樹,如果我們用這種計算節點編號的方式,就根本沒有用了。所以我們這里所有的節點編號,都是人為規定的,玄機就在這個計數變量:tot上。

如果當前去到的節點pos為0,那就說明這個節點沒有被使用過,tot+1變成編號給這個節點,與此同時,因為是遞歸回溯結構,所以這個時候的lson和rson都是已經被確定下來的。(其實tot就是表示當前已經開了多少節點)

這樣,我們就總結出了動態開點的玄妙之處:就是節點的編號和節點左右兒子的編號都是亂序的,是我們臨到使用之時現加上去的。

囑咐幾句:這個參數變量pos前面的取址符&是不能省略的,因為這個編號是不隨遞歸修改的定值。

簡單線段樹可以用數組存,但是動態開點的必須用結構體,因為lson和rson是人為規定的。

差不多就這樣?

祝同志們AK IOI!


免責聲明!

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



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