1. 程式人生 > >線段樹模板整理

線段樹模板整理

綜述

線段樹的原理:將[1,n]分解成若干特定的子區間(數量不超過4*n),然後,將每個區間[L,R]都分解為少量特定的子區間,通過對這些少量子區間的修改或者統計,來實現快速對[L,R]的修改或者統計。
作用:對編號連續的一些點的區間資訊進行修改或者統計操作
主要操作:區間查詢、點更新、區間更新
時間複雜度:修改和統計的複雜度都是O(log(N))

由原理可以看出線段樹維護的資訊必須滿足區間加法
如:
數字之和——總數字之和 = 左區間數字之和 + 右區間數字之和
最大公因數(GCD)——總GCD = gcd( 左區間GCD , 右區間GCD );
最大值——總最大值=max(左區間最大值,右區間最大值)

線段樹原理的詳細分析及應用可以參考一篇寫得特別好的博文:線段樹詳解
這篇部落格完全可以作為學習線段樹的指南及訓練線段樹的參考。

模板

為了規範自己的寫法,所以就整理一下模板。
以下模板ans[]存的是區間和,若存其他符合區間加法的資訊,需要相應改程式碼。

(0)定義

const int MAXN=50010;
int a[MAXN],ans[MAXN<<2],lazy[MAXN<<2];
//a[]為原序列資訊,ans[]模擬線段樹維護區間和,lazy[]為懶惰標記

(1)更新結點資訊

void PushUp(int rt)
{
    ans[rt]=ans[rt<<1
]+ans[rt<<1|1]; }

(2)建樹

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);
}

(3) 下推懶惰標記

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; } }

(4)點更新

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);
}

(5)區間更新

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);
}

(6)區間查詢

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;
}

(7)呼叫函式

    //建樹   
    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);  

注:若只涉及點更新的題,只需用(1)(2)(4)(6)
若只涉及區間更新的題,需用(1)(2)(3)(5)(6)
若為兩種更新都有,則在所有向子區間查詢或更新前,都需PushDown()