1. 程式人生 > >線段樹入門(一)轉自 WDVXDR

線段樹入門(一)轉自 WDVXDR

  這篇文章依然是左神(就是那個複賽寫錯YESNO,狂丟60分的帥小夥)所寫,在此轉載,供學弟學妹們膜拜,原文出處:https://www.cnblogs.com/wdvxdr/p/7238253.html。   正文如下:   接觸線段樹前我們先看一道比較經典的題目。   HDU 1166 敵兵佈陣   這道題要求我們對一個區間內的單個值進行修改,並查詢一段區間的和。 對於修改,我們很容易暴力的修改,複雜度為O(1),但查詢的複雜度就變成了O(n),總複雜度為O(n*m),由於n和m都很大,所以這樣肯定會超時的。 對於區間和我們常見的操作有字首和,用字首和可以將查詢操作的複雜度降為O(1),但修改操作的複雜度則會升到O(n)。總複雜度也沒有改變。 當這兩種方法都無法滿足題目的需求時,我們可以考慮是否可以將修改和查詢的複雜度均攤一下,使總複雜度降低。   線段樹解決這個問題比較好的工具。 什麼是線段樹

  線段樹是一種用樹形來維護線性資料的一種資料結構。 一 般的線段樹上的每一個節點T[a , b],代表該節點維護了原數列[ a , b ]區間的資訊。對於每一個節點他至少有三個資訊:左端點,右端點,我們需要維護的資訊(在本題中我們維護區間和)。

struct segtree { int l,r;//左右端點 long long sum;//要維護的資訊 }tree[maxn<<2];//一般開4*n防止爆空間

建立線段樹

  由於線段樹是一個二叉樹,而且是一個平衡二叉樹,如果當前結點的編號是i,左端點為L ,右端點為 R , 那麼左兒子的 編號為 i*2 ,左端點為 L ,右端點為 (L + R)/2 ; 同理右兒子的 編號為 i*2+1,左端點為(L+R)/2 ,右端點為 R)。如果當前結點的左端點等於右端點,那麼該節點就是葉子節點,直接在該節點賦值即可。顯然線段樹是遞迴定義的。 複製程式碼

void build(int root,int l,int r)
{
    tree[root].l = l;tree[root].r = r;//初始化左右端點
    if(l==r)//為葉子節點,直接賦值
        tree[root].sum = a[l];
    else
    {
        int mid = (l+r)>>1;
        build(root<<1,l,mid);//建立左子樹
        build(root<<1|1,mid+1,r);//右子樹
        push_up(root);//從下往上維護線段樹
} }

從下往上更新線段樹

void push_up(int x)
{
    tree[x].sum = tree[x<<1].sum + tree[x<<1|1].sum;//因題目不同而不同
}

單點更新   單點更新的方法很好理解,如果目標更新節點在左兒子裡,去左兒子中查詢;反之,在右兒子中。不斷遞迴,知道找到需要維護的節點,更新它,然後從下往上維護線段樹回來。這就是維護的過程,程式碼如下:

void update(int x,int q,long long val)
{
    int L=tree[x].l,R=tree[x].r;
    if(L==q&&R==q)//葉子節點,直接更新
        tree[x].num = val;
    int mid = (L+R)>>1;
    if(mid>=q)  update(x<<1,l,r,val);//在左子樹中
    if(q>mid)   update(x<<1|1,l,r,val);//在右子樹中
    push_up(x);//從下往上維護線段樹
}

區間查詢   題目中讓我們查詢區間求和,不難想到如果當前結點的區間完全被目標區間包含,直接返回當前結點的sum值,否則判斷是否在左子樹,右子樹中,然後分別對左子樹和右子樹進行查詢。具體過程通過以下程式碼理解:

long long query(int x,int l,int r)
{
    int L=tree[x].l,R=tree[x].r;
    if(l<=L&&R<=r)//完全在查詢區間內
        return tree[x].sum;//直接返回值
    else
    {
        long long ans = 0;
        int mid = (L+R)>>1;
        if(mid>=l)  ans += query(x<<1,l,r);//在左子樹中有一部分
        if(r>mid)   ans += query(x<<1|1,l,r);//在右子樹中有一部分
        return ans;//返回答案
    }
}

這樣我們就解決了這道題了。