線段樹(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
[參考百度百科及網友講解]