線段樹模板(1)

2021-09-12 06:24:14 字數 3217 閱讀 4099

本篇只對線段樹的基本應用介紹:即整區間單次改變,區間求和,單點改變和區間改變乙個道理,只把區間變成點。

剩下的線段樹知識點類似:區間求逆序對,區間多次改變(同時+,再*或者/或者-),區間(合併,交),區間過大在改變時對p取模等放在後面學習給出。

若只是需要求區間和或者單點改變,樹狀陣列是個好的選擇,但是其他的就老老實實線段樹了。線段樹由於本身是專門用來處理區間問題的(包括rmq、rsq問題等)。

線段樹只是乙個樹的結構(完全二叉樹),和樹本身沒有關係。

線段樹的空間要開4倍最大的空間,用來存樹的結構。

對於每乙個子節點而言,都表示整個序列中的一段子區間;對於每個葉子節點而言,都表示序列中的單個元素資訊;子節點不斷向自己的父親節點傳遞資訊,而父節點儲存的資訊則是他的每乙個子節點資訊的整合。說到底就是運用分塊的思想,用於達到o(logn)級別的處理速度,log以2為底。

觀察上圖可以得出,左孩子編號是i*2,右孩子編號是i*2+1,(完全二叉樹);

二進位制位左移一位代表著數值∗2,而如果左移完之後再或上1,由於左移完之後最後一位二進位制位上一定會是0,所以∣1等價於+1

#define lson    l,m,rt<<1                 //lson表示rt節點的左孩子

#define rson    m+1,r,rt<<1|1         //rson表示右孩子      後面會多次用到,可以先預處理

先介紹pushup函式,根據二叉樹的特性,從下到上維護,pushup操作的目的是為了維護父子節點之間的邏輯關係。當我們遞迴建樹時,對於每乙個節點我們都需要遍歷一遍,並且電腦中的遞迴實際意義是先向底層遞迴,然後從底層向上回溯,所以開始遞迴之後必然是先去整合子節點的資訊,再向它們的祖先回溯整合之後的資訊。

void pushup(ll rt)
由此得到建樹,由於二叉樹自身的父子節點之間的可傳遞關係,所以可以考慮遞迴建樹(,並且在建樹的同時,我們應該維護父子節點的關係。

void build(ll l,ll r,ll rt)

ll m=(l+r)>>1;

build(lson);

build(rson);

pushup(rt); //此處由於我們是要通過子節點來維護父親節點,所以pushup的位置應當是在回溯時。

}

再說下成段更新,把乙個區間的數全+k,這裡引出線段樹第乙個難點,需要用到延遲標記(或者說懶惰標記),簡單來說就是每次更新的時候不要更新到底,用延遲標記使得更新延遲到下次需要更新 or 詢問到的時候。

我們就需要在每次區間的查詢修改時pushdown一次,以免重複或者衝突或者**。

那麼對於pushdown而言,其實就是純粹的pushup的逆向思維(但不是逆向操作): 因為修改資訊存在父節點上,所以要由父節點向下傳導lazy tag。

void pushdown(ll rt,ll z)

}void updata(ll nl,ll nr,ll k,ll l,ll r,ll rt)

pushdown(rt,r-l+1);

//回溯之前(也可以說是下一次遞迴之前,因為沒有遞迴就沒有回溯)

//由於是在回溯之前不斷向下傳遞,所以自然每個節點都可以更新到

ll m = (l+r)>>1;

if(nl<=m) updata(nl,nr,k,lson);

if(nr>m) updata(nl,nr,k,rson);

pushup(rt);//回溯之後維護父節點

}

區間查詢:和區間改變差不多思想,都是用分塊的方式,不斷遞迴

ll query(ll nl,ll nr,ll l,ll r,ll rt)	

pushdown(rt,r-l+1);//看看有沒有之前懶得沒改的

ll m = (l+r)>>1;

if(nl <= m) ans+=query(nl,nr,lson);

if(m < nr) ans+=query(nl,nr,rson);

return ans;

}

最後給出模板:

#includeusing namespace std;

typedef long long ll;

const ll maxn = 1e5+10;

#define lson l,m,rt<<1

#define rson m+1,r,rt<<1|1

ll a[maxn],lazy[maxn<<2],sum[maxn<<2],maxs[maxn<<2];

ll n,m;

void pushup(ll rt)

void pushdown(ll rt,ll z)

}void build(ll l,ll r,ll rt)

ll m=(l+r)>>1;

build(lson);

build(rson);

pushup(rt);

}void updata(ll nl,ll nr,ll k,ll l,ll r,ll rt)

pushdown(rt,r-l+1);

ll m = (l+r)>>1;

if(nl<=m) updata(nl,nr,k,lson);

if(nr>m) updata(nl,nr,k,rson);

pushup(rt);

}ll query(ll nl,ll nr,ll l,ll r,ll rt)

pushdown(rt,r-l+1);

ll m = (l+r)>>1;

if(nl <= m) ans+=query(nl,nr,lson);

if(m < nr) ans+=query(nl,nr,rson);

return ans;

}int main()else if(w==2)

} return 0;

}

樣例測試:

5 5 //5個數5次操作

1 5 4 2 3

2 2 4   //2是求出a-b區間

1 2 3 2  //1是a-b區間都+k

2 3 4

1 1 5 1

2 1 4

輸出:11820

模板 線段樹 1

區間修改 區間查詢 include define ll long long using namespace std ll a 100003 原數列 tree 400003 線段樹 delta 400003 標記 void update int now update多多益善 void build in...

模板 線段樹 1

題目描述 如題,已知乙個數列,你需要進行下面兩種操作 1.將某區間每乙個數加上x 2.求出某區間每乙個數的和 輸入格式 第一行包含兩個整數n m,分別表示該數列數字的個數和操作的總個數。第二行包含n個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。接下來m行每行包含3或4個整數,表示乙個操作...

模板 線段樹1

洛谷p3374 線段樹的結構與樹狀陣列相似,但線段樹更加通用,維護的資料只要滿足區間加即可。線段樹是一棵二叉樹,每個節點表示乙個區間。假設某個節點表示 l,r mid l r 2,則它的左子節點表示 l,mid 右子節點表示 mid 1,r 若乙個節點的編號為x,則它的左子節點編號為x 2,右子節點...