線段樹

線段樹用于維護(hù)區(qū)間信息(要求滿足結(jié)合律)。與樹狀數(shù)組相比,它可以實(shí)現(xiàn)O({\bf log}\ n)區(qū)間修改,還同時(shí)支持加、乘操作,更具有通用性。

在最后給出動(dòng)態(tài)開點(diǎn)的線段樹。

線段樹的建立

線段樹是一顆平衡二叉樹,母結(jié)點(diǎn)代表整個(gè)區(qū)間的和,越往下區(qū)間越小。線段樹的每個(gè)結(jié)點(diǎn)對(duì)應(yīng)一條線段,但并不保證所有的線段都是線段的結(jié)點(diǎn)。

每個(gè)結(jié)點(diǎn)p的左右節(jié)點(diǎn)編號(hào)分別為2p2p+1,如果節(jié)點(diǎn)p儲(chǔ)存區(qū)間[a,b]的和,那么子節(jié)點(diǎn)分別存儲(chǔ)[a,mid][mid+1,r],其中mid=\left \lfloor \frac{a+b}{2} \right \rfloor。

左節(jié)點(diǎn)對(duì)應(yīng)的區(qū)間長度與右節(jié)點(diǎn)數(shù)相同或者比之恰好多1。

區(qū)間修改

線段樹能夠?qū)崿F(xiàn)高效的區(qū)間修改是由于其引入了一個(gè)懶標(biāo)記/延遲標(biāo)記。懶標(biāo)記的存在使區(qū)間修改不再繼續(xù)遞歸下去,而去打上一個(gè)懶標(biāo)記,將來要用到他的子區(qū)間時(shí),再向下傳遞。

懶標(biāo)記如其名,只有要用到子區(qū)間時(shí)它的子區(qū)間才會(huì)在向下傳遞一層(你推他一下他走一步的感覺)。

在進(jìn)行區(qū)間修改時(shí),從最大的區(qū)間開始,遞歸向下處理。遞歸過程中,記當(dāng)前節(jié)點(diǎn)為node,當(dāng)前區(qū)間為[cl,cr],目標(biāo)區(qū)間為[l,r],我們會(huì)遇到三種情況:

  1. 當(dāng)前區(qū)間于目標(biāo)區(qū)間無交集,此時(shí)遞歸結(jié)束。
  2. 當(dāng)前區(qū)間在目標(biāo)區(qū)間之中,此時(shí)更新當(dāng)前區(qū)間,并打上懶標(biāo)記(此前可能存在標(biāo)記),遞歸結(jié)束。
  3. 當(dāng)前區(qū)間與目標(biāo)區(qū)間相交但并不包含在目標(biāo)之中,將區(qū)間進(jìn)行二分操作,并把懶標(biāo)記傳遞給兩個(gè)子區(qū)間(此時(shí)可能存在之前的標(biāo)記),清空當(dāng)前區(qū)間的懶標(biāo)記,最后繼續(xù)遞歸兩個(gè)子區(qū)間。

區(qū)間查詢

我們此處以查詢區(qū)間的最大值為例,在遞歸查詢過程中,記當(dāng)前節(jié)點(diǎn)為node,當(dāng)前區(qū)間為[cl,cr],目標(biāo)區(qū)間[l,r],我們會(huì)遇到三種情況:

  1. 當(dāng)前區(qū)間與目標(biāo)區(qū)間無交集,此時(shí)遞歸結(jié)束,并返回0。
  2. 當(dāng)前區(qū)間在目標(biāo)區(qū)間之中,此時(shí)遞歸結(jié)束,并返回當(dāng)前區(qū)間的值。
  3. 當(dāng)前區(qū)間與目標(biāo)區(qū)間相交但并不包含在目標(biāo)之中,此時(shí)再遞歸的向下進(jìn)行查詢,最后將兩個(gè)區(qū)間中的最大值返回。

代碼示例

Leetcode 731. 我的日程安排表 II為例,使用動(dòng)態(tài)指針實(shí)現(xiàn)的動(dòng)態(tài)開點(diǎn)的代碼為:

class MyCalendarTwo {

    class Node {
        int val, add;
        Node l, r;
    }

    int N = (int) 1e9 + 1;
    Node root;

    public MyCalendarTwo() {
        root = new Node();
    }

    public void update(Node node, int cl, int cr, int l, int r) {
        if (l <= cl && cr <= r) {
            node.add += 1;
            node.val += 1;
            return;
        }
        pushdown(node);
        int m = cl + cr >> 1;
        if (l <= m) update(node.l, cl, m, l, r);
        if (r > m) update(node.r, m + 1, cr, l, r);
        pushup(node);
    }

    public void pushdown(Node node) {
        if (node.l == null) node.l = new Node();
        if (node.r == null) node.r = new Node();
        node.l.add += node.add;
        node.l.val += node.add;
        node.r.add += node.add;
        node.r.val += node.add;
        node.add = 0;
    }

    public void pushup(Node node) {
        node.val = Math.max(node.l.val, node.r.val);
    }

    public boolean query(Node node, int cl, int cr, int l, int r) {
        if (l <= cl && cr <= r) return node.val < 2;
        pushdown(node);
        int m = cl + cr >> 1;
        if (l <= m && !query(node.l, cl, m, l, r)) return false;
        if (r > m && !query(node.r, m + 1, cr, l, r)) return false;
        return true;
    }

    public boolean book(int start, int end) {
        boolean ans = query(root, 0, N, start, end - 1);
        if (ans) update(root, 0, N, start, end - 1);
        return ans;
    }
}

遞歸過程:

線段樹-01.png

題目鏈接

  1. 699. 掉落的方塊
  2. 731. 我的日程安排表 II
  3. 732. 我的日程安排表 III
  4. 2276. 統(tǒng)計(jì)區(qū)間中的整數(shù)數(shù)目

參考文獻(xiàn):

  1. 算法學(xué)習(xí)筆記(14): 線段樹
  2. 【宮水三葉】線段樹(動(dòng)態(tài)開點(diǎn))的兩種方式
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容