本篇只對線段樹的基本應用介紹:即整區間單次改變,區間求和,單點改變和區間改變乙個道理,只把區間變成點。
剩下的線段樹知識點類似:區間求逆序對,區間多次改變(同時+,再*或者/或者-),區間(合併,交),區間過大在改變時對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,右子節點...