線段樹詳解

一:定義html

首先要明確線段樹的定義,線段樹是一顆樹,並且是徹底二叉樹。同時線段樹的每一個節點表示一個區間,左子樹和右子樹分別表示這個區間的左半邊和右半邊。算法

即將區間[L,R]分解成[L,MID]和[MID+1,R],假設根的高度爲1,樹高爲(n>1)ui

下圖展現了區間[1,13]的分解過程spa

二:原理htm

上圖中每一個節點存儲本身對應區間的信息。blog

(1)單點修改遞歸

假設要修改1號節點,不難發現只要修改[1,13]、[1,7]、[1,4]、[1,2]、[1,1]這幾個節點的信息,也就是更新1號節點到根節點的這條鏈上的全部信息get

因此最大修改次數就是樹的高度class

(2)區間查詢變量

咱們能夠將一個[1,n]的線段樹分紅若干個連續不相交區間,嗯大概是

詳細證實能夠參考https://www.cnblogs.com/AC-King/p/7789013.html

(3)區間修改

樸素算法:把區間內的每一個點單點修改(好像有那麼一點點...暴力)

那該怎麼辦呢,這時候一個神奇的東西出現了——懶惰標記(Lazy tag)

咱們把對節點的修改狀況儲存在標記裏,在訪問一個節點的時候,「順便」把它的標記傳遞給它的兒子節點,也就是懶惰標記的下放。

實現思路(重點)

<1>增長一個新的變量用來存儲Lazy tag

<2>遞歸到這個節點時,只更新這個節點的狀態,並把當前的更改值累積到標記裏

<3>當須要遞歸這個節點的子節點的時候,標記下傳給子節點

  ①當前節點的懶惰標記累積到子節點的懶惰標記中

  ②修改子節點狀態(原狀態+子節點區間大小*父節點傳下來的懶惰標記)

  ③父節點懶惰標記清零

三:代碼實現

 

const int MAXN=50010;
int a[MAXN],ans[MAXN<<2],lazy[MAXN<<2];
//a[]爲原序列信息,ans[]模擬線段樹維護區間和,lazy[]爲懶惰標記
void PushUp(int rt)
{
    ans[rt]=ans[rt<<1]+ans[rt<<1|1];
}
void Build(int l,int r,int rt)
{
    if (l==r)
    {
        ans[rt]=a[l];
        return;
    }
    int mid=(l+r)>>1;
    Build(l,mid,rt<<1);
    Build(mid+1,r,rt<<1|1);
    PushUp(rt);
}
void PushDown(int rt,int ln,int rn)//ln表示左子樹元素結點個數,rn表示右子樹結點個數
{
    if (lazy[rt])
    {
        lazy[rt<<1]+=lazy[rt];
        lazy[rt<<1|1]+=lazy[rt];
        ans[rt<<1]+=lazy[rt]*ln;
        ans[rt<<1|1]+=lazy[rt]*rn;
        lazy[rt]=0;
    }
}
void Add(int L,int C,int l,int r,int rt)
{
    if (l==r)
    {
        ans[rt]+=C;
        return;
    }
    int mid=(l+r)>>1;
    //PushDown(rt,mid-l+1,r-mid); 若既有點更新又有區間更新,須要這句話
    if (L<=mid)
        Add(L,C,l,mid,rt<<1);
    else
        Add(L,C,mid+1,r,rt<<1|1);
    PushUp(rt);
}
void Update(int L,int R,int C,int l,int r,int rt)
{
    if (L<=l&&r<=R)
    {
        ans[rt]+=C*(r-l+1);
        lazy[rt]+=C;
        return;
    }
    int mid=(l+r)>>1;
    PushDown(rt,mid-l+1,r-mid);
    if (L<=mid) Update(L,R,C,l,mid,rt<<1);
    if (R>mid) Update(L,R,C,mid+1,r,rt<<1|1);
    PushUp(rt);
}
LL Query(int L,int R,int l,int r,int rt)
{
    if (L<=l&&r<=R)
        return ans[rt];
    int mid=(l+r)>>1;
    PushDown(rt,mid-l+1,r-mid);//若更新只有點更新,不須要這句
    LL ANS=0;
    if (L<=mid) ANS+=Query(L,R,l,mid,rt<<1);
    if (R>mid) ANS+=Query(L,R,mid+1,r,rt<<1|1);
    return ANS;
}
int main()
{
    //建樹   
    Build(1,n,1);   
    //點更新  
    Add(L,C,1,n,1);  
    //區間修改   
    Update(L,R,C,1,n,1);  
    //區間查詢   
    int ANS=Query(L,R,1,n,1);  
}
相關文章
相關標籤/搜索