普通線段樹——高階資料結構入門
一.線段樹引入.
線段樹,是一種基於分治思想的高階樹形資料結構,可以支援滿足快速合併性質的資訊維護.
線段樹作為較為容易也較傳統的高階資料結構,應用廣泛,在高階資料結構中佔有重要的地位.
二.線段樹概述.
我們先來回憶一下分治,分治的一個應用就是想要得到一個區間的值,這個值不能直接求得,但是可以藉助和來快速得到,而且對於任意一個x,的值也可以快速得到,那麼就可以通過分治來求得.
一個經典的演算法就是歸併排序,它要排序區間,那麼就先排好區間和,合併區間和已經排好的兩個序列.然後也不能直接排好,就把這個區間也通過兩個子區間的資訊合併...直到區間變成了,這個區間直接有序了,就可以直接返回求得排好序的序列.
那麼我們把上述分治的過程畫成一張圖(假設是區間)的話:
我們發現這其實是一棵樹,而且我們可以把這個東西顯式建樹,就可以得到一棵線段樹.
三.基本操作Merge.
既然一個區間的資訊要通過區間和的資訊合併來得到,那麼自然要寫一個函式來實現這個功能了.
雖然一般來說,這個函式的程式碼量通常很小,都不會單獨寫一個函式,但是在這裡還是寫一下吧,這裡以維護區間和為例:
tree Merge(tree a,tree b){
tree c;
c.l=a.l;c.r=b.r;
c.sum=c.sum+b.sum;
return c;
}
四.建樹Build.
建樹的過程其實就是一個分治,然後把每一個節點的資訊存下來.
這裡我們依然以維護區間和為例,設a陣列為初始的值的情況,那麼就分治到葉子(區間滿足),就把資訊直接存到葉子上,其它節點的資訊就通過兩個兒子的資訊合併就可以了.
由於我們的分治就是將所有點操作了一遍,而這棵完全二叉樹的節點個數為2n-1,所以建樹是的.
具體程式碼如下:
void Build(int L,int R,int k=1){ tr[k].l=L;tr[k].r=R; if (L==R){ tr[k].sum=a[L]; return; } int mid=L+R>>1; Build(L,mid,k<<1);Build(mid+1,R,k<<1|1); tr[k]=Merge(tr[k<<1],tr[k<<1|1]); }
五.單點操作.
我們考慮一個單點操作,比如說單點查詢一個點x的值.這個應該比較容易,可以從根開始不斷往下遞迴找到葉子節點,然後直接返回這個葉子的資訊即可.
我們在考慮一個單點修改的操作,比如說給一個點x加上v,那麼我們可以找到葉子,講葉子的資訊直接加上v.但是葉子發生改變後,會使該葉子的所有祖先的資訊發生改變,所以我們要在回溯的時候,再讓經過的節點進行merge操作.
由於每次操作最多隻會經過一條從根到葉子的鏈,長度為樹高,而樹高為,所以單點操作的時間複雜度為.
具體程式碼如下:
int Query(int x,int k=1){
if (tr[k].l==tr[k].r) return tr[k].sum;
int mid=tr[k].l+tr[k].r>>1;
if (x<=mid) return Query(x,k<<1);
else return Query(x,k<<1|1);
}
void Add(int x,int num,int k=1){
if (tr[k].l==tr[k].r){
tr[k].sum+=num;
return;
}
int mid=tr[k].l+tr[k].r>>1;
if (x<=mid) Add(x,num,k<<1);
else Add(x,num,k<<1|1);
tr[k]=Merge(tr[k<<1],tr[k<<1|1]);
}
六.區間查詢操作.
對於區間查詢資訊並的操作,我們當然可以利用上面單點查詢每一個點,然後將資訊並起來,但是這樣就會使時間複雜度變為,十分不優美.
我們考慮從根節點向下的時候,若要查詢的區間包括在一個節點的左兒子,直接往左兒子向下;右兒子同理;若左右兒子都有,則左右兒子都遞迴進入,最後將兩個兒子中找到的答案合併起來就行了.
但是這樣的時間複雜度貌似還是,但其實我們可以證明這是的.
證明如下:
首先,這個區間在每一層,都只會遍歷到最多4個節點.
若會遍歷到4個以上節點,則必定有兩個節點表示的區間是直接被包含且是兄弟的關係,那麼這兩個節點應該會在上一層就被遍歷過了,不會在這一層再被遍歷了.
所以,每一層最多會遍歷到4個節點,所以時間複雜度為.
證畢.
那麼區間查詢程式碼如下(以區間加為例):
int Query(int L,int R,int k=1){
if (L==tr[k].l&&R==tr[k].r) return tr[k].sum;
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) return Query(L,R,k<<1);
else if (L>mid) return Query(L,R,k<<1|1);
else return Query(L,mid,k<<1)+Query(mid+1,R,k<<1|1);
}
七.區間修改與lazy-tag.
區間修改,我們以給區間的每個數加上y為例.
我們考慮與區間查詢類似,直接找到每一個需要修改的修改,然後對每一個區間直接加上y*區間長度.
但是這樣子的話,被修改的節點下所有節點都要修改,但這樣做就會無法修改,暴力修改又太慢,我們該怎麼辦?
我們引入lazy-tag的概念,給每一個修改的節點在打上一個tag標記,表示這個節點下面的所有節點都要增加一個值.
那麼,當我們要查詢點的時候,對所有經過的節點的tag往下推,對下面的節點進行修改,我們把這個過程寫成一個函式pushdown.
那麼我們就可以做到修改啦,但是要注意若有tag,最好在任何操作經過任何節點都pushdown.
程式碼如下:
void Update_add(int k,int num){ //為了簡潔寫一個Update函式
tr[k].sum+=num*(tr[k].r-tr[k].l+1);
tr[k].tag+=num;
}
void Pushdown(int k){
Update_add(k<<1,tr[k].tag);Update_add(k<<1|1,tr[k].tag);
tr[k].tag=0;
}
void Add(int L,int R,int num,int k=1){
if (L==tr[k].l&&R==tr[k].r){
Update_add(k,num);
return;
}
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) Add(L,R,num,k<<1);
else if (L>mid) Add(L,R,num,k<<1|1);
else Add(L,mid,num,k<<1),Add(mid+1,R,num,k<<1);
tr[k]=Merge(tr[k<<1],tr[k<<1|1]);
}
八.例題與程式碼.
現在我們拿出三道例題,分別為luogu3372,luogu3374,luogu3368.
luogu3372程式碼:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500000;
int n,m;
LL a[N+9];
struct tree{
int l,r;
LL sum,tag;
}tr[N*4+9];
void Update_add(int k,LL num){
tr[k].sum+=num*(tr[k].r-tr[k].l+1);
tr[k].tag+=num;
}
void Pushdown(int k){
Update_add(k<<1,tr[k].tag);Update_add(k<<1|1,tr[k].tag);
tr[k].tag=0LL;
}
void Build(int L,int R,int k=1){
tr[k].l=L;tr[k].r=R;
if (L==R){
tr[k].sum=a[L];
return;
}
int mid=L+R>>1;
Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void Add(int L,int R,LL num,int k=1){
if (L==tr[k].l&&R==tr[k].r){
Update_add(k,num);
return;
}
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) Add(L,R,num,k<<1);
else if (L>mid) Add(L,R,num,k<<1|1);
else Add(L,mid,num,k<<1),Add(mid+1,R,num,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
LL Query(int L,int R,int k=1){
if (L==tr[k].l&&R==tr[k].r) return tr[k].sum;
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) return Query(L,R,k<<1);
else if (L>mid) return Query(L,R,k<<1|1);
else return Query(L,mid,k<<1)+Query(mid+1,R,k<<1|1);
}
Abigail into(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%lld",&a[i]);
}
Abigail work(){
Build(1,n);
}
Abigail getans(){
int opt,l,r;
LL x;
for (int i=1;i<=m;++i){
scanf("%d",&opt);
if (opt==1){
scanf("%d%d%lld",&l,&r,&x);
Add(l,r,x);
}else{
scanf("%d%d",&l,&r);
printf("%lld\n",Query(l,r));
}
}
}
int main(){
into();
work();
getans();
return 0;
}
luogu3374程式碼:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500000;
int n,m;
LL a[N+9];
struct tree{
int l,r;
LL sum;
}tr[N*4+9];
void Build(int L,int R,int k=1){
tr[k].l=L;tr[k].r=R;
if (L==R){
tr[k].sum=a[L];
return;
}
int mid=L+R>>1;
Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void Add(int x,LL num,int k=1){
if (tr[k].l==tr[k].r){
tr[k].sum+=num;
return;
}
int mid=tr[k].l+tr[k].r>>1;
if (x<=mid) Add(x,num,k<<1);
else Add(x,num,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
LL Query(int L,int R,int k=1){
if (tr[k].l==L&&tr[k].r==R) return tr[k].sum;
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) return Query(L,R,k<<1);
else if (L>mid) return Query(L,R,k<<1|1);
else return Query(L,mid,k<<1)+Query(mid+1,R,k<<1|1);
}
Abigail into(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%lld",&a[i]);
}
Abigail work(){
Build(1,n);
}
Abigail getans(){
int opt,l,r;
LL x;
for (int i=1;i<=m;++i){
scanf("%d",&opt);
if (opt==1){
scanf("%d%lld",&l,&x);
Add(l,x);
}else{
scanf("%d%d",&l,&r);
printf("%lld\n",Query(l,r));
}
}
}
int main(){
into();
work();
getans();
return 0;
}
luogu3368程式碼:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500000;
int n,m;
LL a[N+9];
struct tree{
int l,r;
LL sum,tag;
}tr[N*4+9];
void Update_add(int k,LL num){
tr[k].sum+=num*(tr[k].r-tr[k].l+1);
tr[k].tag+=num;
}
void Pushdown(int k){
Update_add(k<<1,tr[k].tag);Update_add(k<<1|1,tr[k].tag);
tr[k].tag=0;
}
void Build(int L,int R,int k=1){
tr[k].l=L;tr[k].r=R;
if (L==R){
tr[k].sum=a[L];
return;
}
int mid=L+R>>1;
Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void Add(int L,int R,LL num,int k=1){
if (tr[k].l==L&&tr[k].r==R){
Update_add(k,num);
return;
}
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (R<=mid) Add(L,R,num,k<<1);
else if (L>mid) Add(L,R,num,k<<1|1);
else Add(L,mid,num,k<<1),Add(mid+1,R,num,k<<1|1);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
LL Query(int x,int k=1){
if (tr[k].l==tr[k].r) return tr[k].sum;
Pushdown(k);
int mid=tr[k].l+tr[k].r>>1;
if (x<=mid) return Query(x,k<<1);
else return Query(x,k<<1|1);
}
Abigail into(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
scanf("%lld",&a[i]);
}
Abigail work(){
Build(1,n);
}
Abigail getans(){
int opt,l,r;
LL x;
for (int i=1;i<=m;++i){
scanf("%d",&opt);
if (opt==1){
scanf("%d%d%lld",&l,&r,&x);
Add(l,r,x);
}else{
scanf("%d",&l);
printf("%lld\n",Query(l));
}
}
}
int main(){
into();
work();
getans();
return 0;
}