[線段樹系列] 普通線段樹

線段樹是一種強大的數據結構,用於維護區間、圖、樹等各類數據。c++

線段樹的「強大」體如今它面對各類類型的數據都有應付的方式,並且不斷有「改進」版線段樹的產生。算法

線段樹是基於遞歸和分治思想的數據結構,通常用於維護具備「區間可加性」的數據。數組

什麼是」區間可加性「呢,舉幾個例子:數據結構

區間和,區間最大最小值,區間LCA,區間質數個數ui

這些東西都有共同的特性:f(x,y)=f(f(x,z),f(z,y)),z∈[x,y]spa

因而咱們就能夠用線段樹來維護。code

隨手畫了張線段樹的圖,它大概長這樣:blog

是否是很神奇?遞歸

它是怎麼維護數據的呢?( 以維護區間數據爲例 )get

讓咱們用區間最大值爲例:

假設原數組a是{1,2,3,4,5,6} (我習慣下標從1開始)

咱們把原數組插入線段樹,看看它各個節點的值:

假設咱們要查詢區間 [2,4] 的值:

咱們發現並無 [2,4] 這個節點,那怎麼查詢呢?

咱們計算出 [2,4] 的mid值,mid=(l+r)/2=3。

而後咱們查詢區間 [2,3] 和區間 [4,4] ( 即區間[l,mid]和區間[mid+1,r] )。

區間 [4,4] 是葉子節點,返回它的值4。

回到 [2,3] 咱們繼續遞歸計算max( [2,2],[3,3] )返回3

獲得最後的最大值4

這一段的代碼:

int query(int p,int l,int r){ if(l<=l(p) && r>=r(p))return val(p); int mid=(l(p)+r(p))>>1; int val=-INF; if(l<=mid)val=max(val,query(p<<1,l,r)); if(r>mid)val=max(val,query(p<<1|1,l,r)); return val; }

等等,既然要維護數據,怎麼能不支持修改呢?

線段樹支持單點修改和區間修改。

單點修改很是簡單,我這裏就不介紹了。

直接來區間修改。

咱們每次更新一個值都要把全部包含它的節點更新一次。

可是若是更新一整個區間,這個更新量是很是大的,做爲一種高效數據結構是不會容許這麼慢的修改的。

因而咱們引進一個」打標記「的思想。

也就是給咱們要更新的值先打上」你已經被修改了「的標記,等咱們要用它的時候在改它。

在線段樹中被稱爲」下發懶標記「——pushdown lazytag

下傳標記的代碼( 以維護區間和爲例 ):

void pushdown(int p){ if(add(p)){ val(p<<1)+=add(p)*(r(p<<1)-l(p<<1)+1); val(p<<1|1)+=add(p)*(r(p<<1|1)-l(p<<1|1)+1); add(p<<1)+=add(p); add(p<<1|1)+=add(p); add(p)=0; } }

這裏咱們的懶標記是加(add),懶標記還能夠是乘(mul),或者加乘混合(須要符合運算法則)

接下來放建樹的代碼:

void pushup(int p){ val(p)=val(p<<1)+val(p<<1|1); } void build(int p,int l,int r){ l(p)=l,r(p)=r;//保存每一個節點的左右兒子的編號
    if(l==r){ val(p)=a[l]; return; }pushdown(p); int mid=(l+r)>>1; build(p<<1,l,mid);build(p<<1|1,mid+1,r);//遞歸建樹 
    pushup(p);//更新值
}

上一份維護區間和的完整代碼吧:

 

#include<bits/stdc++.h>
using namespace std; inline int read(){ int data=0,w=1;char ch=0; while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')w=-1,ch=getchar(); while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar(); return data*w; }//快讀
const int maxn=100010; int n,m,a[maxn]; struct SegmentTree{ int l,r;//左右節點編號
    int val,add;//節點值,標記
    #define l(x) tree[x].l
    #define r(x) tree[x].r
    #define val(x) tree[x].val
    #define add(x) tree[x].add }tree[maxn<<2]; void pushup(int p){ val(p)=val(p<<1)+val(p<<1|1);//更新,一個節點用它的左右兩個節點更新
} void build(int p,int l,int r){//建樹代碼
    l(p)=l,r(p)=r; if(l==r){ val(p)=a[l];return; } int mid=(l+r)>>1; build(p<<1,l,mid);build(p<<1|1,mid+1,r); pushup(p); } void pushdown(int p){//下傳懶標記
    if(add(p)){ val(p<<1)+=add(p)*(r(p<<1)-l(p<<1)+1); val(p<<1|1)+=add(p)*(r(p<<1|1)-l(p<<1|1)+1); add(p<<1)+=add(p); add(p<<1|1)+=add(p); add(p)=0; } } int query(int p,int l,int r){//查詢
    if(l<=l(p) && r>=r(p))return val(p); pushdown(p); int mid=(l(p)+r(p))>>1; int ret=0; if(l<=mid)ret+=query(p<<1,l,r); if(r>mid)ret+=query(p<<1|1,l,r); return ret; } void update(int p,int l,int r,int d){//修改
    if(l<=l(p) && r>=r(p)){ val(p)+=d*(r(p)-l(p)+1);add(p)+=d; return; } pushdown(p); int mid=(l(p)+r(p))>>1; if(l<=mid)update(p<<1,l,r,d); if(r>mid)update(p<<1|1,l,r,d); pushup(p); } int main(){ n=read();m=read();//讀入數據個數,操做個數
    for(int i=1;i<=n;i++)a[i]=read();//讀入數據
    build(1,1,n);//建樹
    int opt,l,r,d; while(m--){ opt=read();l=read();r=read(); if(opt==1)//查詢
            printf("%d\n",query(1,l,r)); else{ d=read();//區間加上這個值
            update(1,l,r,d);//修改
 } } return 0; }

 

撰文不易,但願能幫到各位,下一篇講動態開點線段樹,本系列持續更新

相關文章
相關標籤/搜索