線段樹基本操做(Segment Tree)

線段樹(Segment Tree)
node

入門模板題 洛谷oj P3372函數

題目描述ui

  如題,已知一個數列,你須要進行下面兩種操做:spa

  1.將某區間每個數加上x指針

  2.求出某區間每個數的和code

輸入格式blog

  第一行包含兩個整數N、M,分別表示該數列數字的個數和操做的總個數。遞歸

  第二行包含N個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。get

  接下來M行每行包含3或4個整數,表示一個操做,具體以下:it

  操做1: 格式:1 x y k 含義:將區間[x,y]內每一個數加上k

  操做2: 格式:2 x y 含義:輸出區間[x,y]內每一個數的和

輸出格式

  輸出包含若干行整數,即爲全部操做2的結果。

輸入樣例

5 5

1 5 4 2 3

2 2 4

1 2 3 2

2 3 4

1 1 5 1

2 1 4

輸出樣例

11

8

20

數據範圍

對於30%的數據:N<=8,M<=10

對於70%的數據:N<=1000,M<=10000

對於100%的數據:N<=100000,M<=100000

 

  才學會寫線段樹不久……隨便扯一篇筆記orz

 

0x00 線段樹概念

  線段樹是一種二叉搜索樹。它將一個區間劃分紅一些單元區間,每一個單元區間對應線段樹中的一個葉結點。

  插入(刪除)操做的時間複雜度爲O(logn)。

 

0x01 樹形結構及建樹

  做爲一棵樹,線段樹的結構體大概是這樣的:

struct SegmentTree{        //樹的結構體 
    long long l,r;    //覆蓋的區間的左右指針
    long long value;    //該節點上維護的值
    long long tag;    //lazy標記,下面會寫到,這也是線段樹的精髓
}node[MAX_N*4+5];

 

  要想建一棵線段樹,能夠利用線段樹是個二叉樹的性質,進行遞歸建樹:

void build(REG long long p,REG long long l,REG long long r){    //遞歸建樹 
    node[p].l=l,node[p].r=r;
    if (l==r){    //訪問到了最底部,不可再分 
        node[p].value=read();
        return ;
    }
    long long mid=(l+r)>>1;
    build(p<<1,l,mid);
    build((p<<1)+1,mid+1,r);
    node[p].value=node[p<<1].value+node[(p<<1)+1].value;
}

  

  這樣咱們就擁有了一棵樹。

0x02 lazy標記(懶標記)

  上面提到過,線段樹的精髓是lazy標記。無論是要查詢仍是更改數列,都繞不開它。

  若是要修改一段區間(好比同時都增長k),最容易想到的方法是暴力枚舉,一個個修改。這樣操做m次,最壞時間複雜度是O(mn)。顯然是過不去的。

  因此咱們能夠想:若是不修改那麼多節點呢?很容易想到,咱們只關心查詢須要用到的節點——並非每個被修改的節點都會在查詢中被訪問到的。打個比方:假設只有一次修改,一次詢問。修改修改[1,16],而詢問只關心[1,8],那麼咱們對於[9,16]的修改都是沒有意義的。

  能夠想到,若是點i的兩個兒子都要同時增長k,考慮先不修改兩個兒子,而給點i打一個標記,標記一下i的兩個兒子都要被修改。

  若是在下面的詢問裏要訪問到i的兩個兒子之一,咱們再對i的兩個兒子進行修改(或下傳標記),再把標記歸零。若是兩個兒子都會被訪問到,就直接取i的值(i點維護的value值是兩個兒子節點的和)。

  這差很少就是lazy標記的思想,時間複雜度就降到了O(mlogn)。

  寫一個下傳標記的函數:

 

void push_down(REG const long long& p){
    node[p<<1].value+=(node[p].tag*(node[p<<1].r-node[p<<1].l+1));
    node[(p<<1)+1].value+=(node[p].tag*(node[(p<<1)+1].r-node[(p<<1)+1].l+1));
    node[p<<1].tag+=node[p].tag;
    node[(p<<1)+1].tag+=node[p].tag;
    node[p].tag=0;
}

 

  按照這樣的思想,咱們就能夠寫出線段樹了。其餘的在最終代碼裏寫了,再也不贅述。

 

0x03 AC代碼

#include <cstdio>
#define REG register
#define MAX_N 100000

using namespace std;

long long n,m;
long long a[MAX_N+5];

long long ch,x,y,z;

inline long long read(){    //快速讀入 
    REG long long ch=getchar(),x=0,f=1;
    while (ch<'0'||ch>'9'){
        if (ch=='-')    f=-1;
        ch=getchar();
    } while (ch<='9'&&ch>='0'){
        x=x*10+ch-'0';
        ch=getchar();
    }return x*f;
}

struct SegmentTree{        //樹的結構體 
    long long l,r;
    long long value;
    long long tag;
}node[MAX_N*4+5];

void build(REG long long p,REG long long l,REG long long r){    //遞歸建樹 
    node[p].l=l,node[p].r=r;
    if (l==r){    //訪問到了最底部,不可再分 
        node[p].value=read();
        return ;
    }
    long long mid=(l+r)>>1;
    build(p<<1,l,mid);
    build((p<<1)+1,mid+1,r);
    node[p].value=node[p<<1].value+node[(p<<1)+1].value;
}

void push_down(REG const long long& p){
    node[p<<1].value+=(node[p].tag*(node[p<<1].r-node[p<<1].l+1));
    node[(p<<1)+1].value+=(node[p].tag*(node[(p<<1)+1].r-node[(p<<1)+1].l+1));
    node[p<<1].tag+=node[p].tag;
    node[(p<<1)+1].tag+=node[p].tag;
    node[p].tag=0;    //標記歸零 
}

void change(REG long long p,REG const long long& x,REG const long long& y,REG long long& z){
    if (x<=node[p].l&&y>=node[p].r){    //是否徹底在區間內 
        node[p].value+=(z*(node[p].r-node[p].l+1));
        node[p].tag+=z;
        return ;
    }
    if (node[p].tag)    push_down(p);    //不徹底在區間內,若是有lazy標記,下傳 
    REG long long mid=(node[p].l+node[p].r)>>1;
    if (x<=mid)    change(p<<1,x,y,z);
    if (y>mid)    change((p<<1)+1,x,y,z);
    node[p].value=node[p<<1].value+node[(p<<1)+1].value;
}

inline long long ask(REG long long p,REG const long long& x,REG const long long& y){
    if (x<=node[p].l&&y>=node[p].r)    return node[p].value;
    push_down(p);
    REG long long mid=(node[p].l+node[p].r)>>1;
    long long ans=0;
    if (x<=mid)    ans+=ask(p<<1,x,y);
    if (y>mid)    ans+=ask((p<<1)+1,x,y);
    return ans; 
}

int main(){
//    freopen("test1.txt","r",stdin);
    n=read(),m=read();
    build(1,1,n);
    REG long long ch;
    while (m--){
        ch=read();
        if (ch==1){
            x=read(),y=read(),z=read();
            change(1,x,y,z);
        }
        else{
            x=read(),y=read();
            printf("%lld\n",ask(1,x,y));
        }
    }
    return 0;
} 

 

   若是寫的有哪裏不對,歡迎指出,輕噴orz

 

 

 

[參考百度百科及網友講解]

相關文章
相關標籤/搜索