關於線段樹的感悟(Segment Tree)

線段樹的感悟 : 學過的東西必定要多回頭看看,否則真的會忘個乾乾淨淨。

線段樹的 Introduction :

English Name : Segment Tree
顧名思義 : 該數據結構由兩個重要的東西組成 : 線段,樹,連起來就是在樹上的線段。
想一下,線段有啥特徵 ?
不就是兩個端點中間一條線嗎,哈哈,也能夠這麼理解,但這樣是否是稍微難聽呀,因此
咱們用一個華麗的詞語來描述這個兩點之間的一條線,這個詞語就是不知道哪一個先知發
明的,就是 -- 區間。
因此咱們就可猜測到,因此線段樹必定是用來處理區間問題的。

線段樹長個啥樣子?

展現一個區間  1 - 10 的一顆線段樹,就是這麼個樹東西。

線段樹的基本結構 :

一、線段樹的每一個節點都表明一個區間
二、線段樹具備惟一的根節點,表明的區間的整個統計範圍,[1,N]
三、線段樹的每一個葉節點都表明一個長度爲 1 的元區間 [x,x],也就是咱們原數組中每一個值,原數組中有幾個值
   就有多少個葉子節點(能夠參照上圖瞭解一下)。
四、對於每一個內部節點 [l,r],它的左子節點是 [l,mid],右子節點是 [mid + 1,r],mid = l + r >> 1(向下取整)

線段樹常常處理那些區間問題 ?

一、單點查詢(查詢某個位置上的值是多少)
二、單點修改(修改某個位置上的值)
三、區間查詢(查詢某個區間的 和、最大值、最小值、最大公約數、and so on)
四、區間修改(修改某個區間的值, eg:讓某個區間都 + 一個數、and so on)

線段樹須要注意的地方 :

一、結構體空間必定要開 4 倍,必定要記得看 4 倍(看上面這棵樹,按節點編號咱們能夠看到一共有 25 個節點,但算上空餘的位置呢?)
   會發現有 31 個節點,能夠本身數一下,因此咱們要開原數組的 4 倍,避免出現數組越界,非法訪問的狀況(段錯誤)。
二、區間判斷的時候必定不要寫反(下面寫的時候就知道了,這個坑讓我 Debug 了一個多小時)
三、沒事多打打,模板,就當練手速了。

線段樹的基本操做 :

一、Struct結構體存儲

struct node {
    LL l,r;
    LL sum;  // 看須要向父節點傳送什麼
} tr[maxn << 2];

二、 Build

void pushup(LL u) {
    tr[u].sum = gcd(tr[u << 1].sum,tr[u << 1 | 1].sum);
    return ;
}

void build(LL u,LL l,LL r) {
    tr[u].l = l,tr[u].r = r;  // 初始化(節點 u 表明區間 [l,r])
    if(l == r) {
        tr[u].sum = b[l]; // 遞歸到葉節點賦初值
        return ;
    }
    LL mid = l + r >> 1;      // 折半
    build(u << 1,l,mid);      // 向左子節點遞歸
    build(u << 1 | 1,mid + 1,r); // 向右子節點遞歸
    pushup(u);                // 從下往上傳遞信息
    return;
}

三、Update

void update(LL u,LL x,LL v) {
    if(tr[u].l == tr[u].r) {        // 找到葉節點
        tr[u].sum += v;         // 在某個位置加上一個數
        return ;
    }
    LL mid = tr[u].l + tr[u].r >> 1;
    if(x <= mid) update(u << 1,x,v); // x 屬於左半區間
    else update(u << 1 | 1,x,v);     // x 屬於右半區間
    pushup(u);                       // 從下向上更新信息
    return ;
}

四、Query :

一、若 [l,r] 徹底覆蓋了當前節點表明的區間,則當即回溯。
二、若左子節點與 [l,r] 有重疊部分,則遞歸訪問左子節點。
三、若右子節點與 [l,r] 有重疊部分,則遞歸訪問右子節點。
LL query(int u,int l,int r) {
    if(tr[u].l >= l && tr[u].r <= r) {   // 徹底包含
        return tr[u].sum;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    LL sum = 0;
    if(l <= mid) sum += query(u << 1,l,r);
    if(r > mid) sum += query(u << 1 | 1,l,r);
    return sum; 
}

上述就是線段樹的基本操做,基本上都是圍繞單點問題進行操做,若是要涉及到複雜的區間操做,
例如 : 給區間 [l,r] 每一個數都 + d
這時若是還用上述操做,咱們就須要進行 l - r + 1 次操做,若是有屢次這樣的操做,顯然時間
複雜度會很高,這時候咱們應該選擇什麼樣的方法來下降時間複雜度呢 ?node

Lazy(懶) 標記應運而生

簡單一點來講就是,減小重複的操做,若是說咱們操做的每個數都在一個區間範圍內,那麼
咱們就能夠直接處理這個區間,不須要再一個一個處理,好比上面的給區間的每個數 + d;
假設說咱們已經知道 [l,r] 徹底包含一個區間 [x,y],也就是說 區間[x,y]是 [l,r]的
一個子區間,那麼這個時候咱們是否是直接能夠計算出 [x,y] 這個區間 都 + d 後的值是
多少, (x - y + 1) * d(假設是求和的話),這樣咱們就能夠再也不用去一個一個加,而後
再合併了,咱們知道有這樣的區間後,怎麼用呢?這時候就須要進行標記一下,便於咱們知道
這個地方有一個區間能夠直接處理,不須要再麻煩着向下繼續去處理了,是否是很懶,哈哈。
/*
    懶標記的含義 : 該節點曾經被修改,但其子節點還沒有被更新。
    在後續的指令中,咱們須要從某個節點向下遞歸時,檢查該節點是否具備標記,如有標記,就根據
    標記信息更新 該節點 的兩個子節點,同時爲該節點的兩個子節點增長標記,而後清楚 p 的標記。
*/
void pushdown(int u) {
    if(tr[u].lazy) {    // 節點 u 有標記
        tr[u << 1].sum += tr[u].lazy * (tr[u << 1].r - tr[u << 1].l + 1); // 更新左子節點信息
        tr[u << 1| 1].sum += tr[u].lazy * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1); // 更新右子節點
        tr[u << 1].lazy += tr[u].lazy;     // 給左子節點打延遲標記
        tr[u << 1 | 1].lazy += tr[u].lazy; // 給右子節點打延遲標記
        tr[u].lazy = 0;                    // 清楚父節點的延遲標記(這點很重要)
    }
    return ;
}
加上 Lazy 標記的其餘操做 :
// Build 不變
// Update
void modify(int u,int l,int r,int x) {
    if(tr[u].l >= l && tr[u].r <= r) {  // 徹底覆蓋
        tr[u].sum += (tr[u].r- tr[u].l + 1) * x; // 更新節點信息
        tr[u].lazy += x;                // 給節點打延遲標記
        return ;
    }
    pushdown(u);                        // 下傳延遲標記
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid) modify(u << 1,l,r,x);
    if(r > mid) modify(u << 1 | 1,l,r,x);
    pushup(u);
    return ;
}

// Query
LL query(int u,int l,int r) {
    if(tr[u].l >= l && tr[u].r <= r) {
        return tr[u].sum;
    }
    pushdown(u);                  // 同上
    int mid = tr[u].l + tr[u].r >> 1;
    LL sum = 0;
    if(l <= mid) sum += query(u << 1,l,r);
    if(r > mid) sum += query(u << 1 | 1,l,r);
    return sum; 
}

總結 :

線段樹的操做基本上就這些,哈哈,實際上本身就瞭解這麼多,並且是最近有幾場比賽遇見挺多的,就學了一下,
主要是手得多動動,有時候考察得仍是比較複雜得,先把這些基礎得模板搞懂吧。

例題(模板題):

一、一個簡單的整數問題ios

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 10;
typedef long long LL;

struct node {
    int l,r;
    LL sum,lazy;
}tr[maxn << 2];
int a[maxn];
int n,m;
int l,r;

int main(void) {
    void build(int u,int l,int r);
    void modify(int u,int l,int r,int x);
    LL query(int u,int l,int r);
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i ++) {
        scanf("%d",&a[i]);
    }
    build(1,1,n);
    while(m --) {
        char ch;
        cin >> ch;
        if(ch == 'Q') {
            scanf("%d",&l); 
            printf("%lld\n",query(1,1,l) - query(1,1,l - 1));
        } else {
            int value;
            scanf("%d%d%d",&l,&r,&value);
            modify(1,l,r,value);
        }
    }
    return 0;
} 

void pushup(int u) {
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
    return ;
}

void pushdown(int u) {
    if(tr[u].lazy) {
        tr[u << 1].sum += tr[u].lazy * (tr[u << 1].r - tr[u << 1].l + 1);
        tr[u << 1| 1].sum += tr[u].lazy * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1);
        tr[u << 1].lazy += tr[u].lazy;
        tr[u << 1 | 1].lazy += tr[u].lazy;
        tr[u].lazy = 0;
    }
    return ;
}

void build(int u,int l,int r) {
    tr[u].l = l,tr[u].r = r;
    if(l == r) {
        tr[u].sum = a[l];
        return ;
    }
    int mid = l + r >> 1;
    build(u << 1,l,mid);
    build(u << 1 | 1,mid + 1,r);
    pushup(u);
    return ;
}

void modify(int u,int l,int r,int x) {
    if(tr[u].l >= l && tr[u].r <= r) {
        tr[u].sum += (tr[u].r- tr[u].l + 1) * x;
        tr[u].lazy += x;
        return ;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid) modify(u << 1,l,r,x);
    if(r > mid) modify(u << 1 | 1,l,r,x);
    pushup(u);
    return ;
}

LL query(int u,int l,int r) {
    if(tr[u].l >= l && tr[u].r <= r) {
        return tr[u].sum;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    LL sum = 0;
    if(l <= mid) sum += query(u << 1,l,r);
    if(r > mid) sum += query(u << 1 | 1,l,r);
    return sum; 
}

二、一個簡單的整數問題2數組

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 10;
typedef long long LL;

struct node {
    int l,r;
    LL sum,lazy;
}tr[maxn << 2];
int a[maxn];
int n,m;
int l,r;

int main(void) {
    void build(int u,int l,int r);
    void modify(int u,int l,int r,int x);
    LL query(int u,int l,int r);
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i ++) {
        scanf("%d",&a[i]);
    }
    build(1,1,n);
    while(m --) {
        char ch;
        cin >> ch;
        if(ch == 'Q') {
            scanf("%d%d",&l,&r);    
            printf("%lld\n",query(1,l,r) );
        } else {
            int value;
            scanf("%d%d%d",&l,&r,&value);
            modify(1,l,r,value);
        }
    }
    return 0;
} 

void pushup(int u) {
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
    return ;
}

void pushdown(int u) {
    if(tr[u].lazy) {
        tr[u << 1].sum += tr[u].lazy * (tr[u << 1].r - tr[u << 1].l + 1);
        tr[u << 1| 1].sum += tr[u].lazy * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1);
        tr[u << 1].lazy += tr[u].lazy;
        tr[u << 1 | 1].lazy += tr[u].lazy;
        tr[u].lazy = 0;
    }
    return ;
}

void build(int u,int l,int r) {
    tr[u].l = l,tr[u].r = r;
    if(l == r) {
        tr[u].sum = a[l];
        return ;
    }
    int mid = l + r >> 1;
    build(u << 1,l,mid);
    build(u << 1 | 1,mid + 1,r);
    pushup(u);
    return ;
}

void modify(int u,int l,int r,int x) {
    if(tr[u].l >= l && tr[u].r <= r) {
        tr[u].sum += (tr[u].r- tr[u].l + 1) * x;
        tr[u].lazy += x;
        return ;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid) modify(u << 1,l,r,x);
    if(r > mid) modify(u << 1 | 1,l,r,x);
    pushup(u);
    return ;
}

LL query(int u,int l,int r) {
    if(tr[u].l >= l && tr[u].r <= r) {
        return tr[u].sum;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    LL sum = 0;
    if(l <= mid) sum += query(u << 1,l,r);
    if(r > mid) sum += query(u << 1 | 1,l,r);
    return sum; 
}

做者:Eureka
連接:https://www.acwing.com/activity/content/code/content/198208/
來源:AcWing
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索