線段樹
線段樹是一種二叉搜索樹,類似區間樹,是一個完全二叉樹,它在各個節點保存一條線段(數組中的一段子數組),主要用於高效解決連續區間的動態查詢問題,它基本能保持每個操作的時間複雜度爲O(lgN)。
假定根結點是長度爲2^h的區間,第i層有2^i個結點(層數從0開始),每個結點對應一個長度爲2^(h-i)的區間。最大層編號爲h,所以結點總數爲1+2+4+……+2^h=2^(h+1)-1;所以一般線段樹開結構體時需要兩倍空間。
當整個區間長度不是2的整數冪時,葉子不全在同一層,但樹的最大層編號和結點總數仍滿足上述結論。
對於線段樹中的每一個下標爲i非葉子節點[a,b],它的左兒子下標爲2*i表示的區間爲[a,(a+b)/2],右兒子下標爲2*i+1表示的區間爲[(a+b)/2+1,b]。因此線段樹是平衡二叉樹,最後的子節點數目爲N,即整個線段區間的長度。
可以邊看下面的圖邊理解我說的話。
把上面的圖和話理解了基本就瞭解線段樹了,接下來看看線段樹的代碼。
線段樹有單點更新和區間更新兩種情況,我以hihoCoder1077爲例講一下線段樹的單點更新。
hihoCoder
這題就是線段樹單點更新的裸題。就是可以動態修改區間中的一個數,動態詢問區間最小值。具體看代碼註釋
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e6+5;
//一般線段樹需要開兩倍空間,這裏1e6大概是2的20次方,所以我開2^21
const int MAX = 1<<21;
//node_id[i]是區間中第i個數在線段樹中的節點編號
int n,q,a,b,c,ans,node_id[maxn];
struct segment_tree{
int l,r,w; //l,r區間左右邊界,w區間最小值
}tree[MAX];
void build_segment_tree(int i,int l,int r) //建立線段樹
{
tree[i].l=l;
tree[i].r=r;
tree[i].w=INF;
if(l==r) //葉子節點
{
node_id[l] = i; //保存葉子節點在線段樹中的節點編號
return;
}
//從該節點向下建立線段樹
build_segment_tree(i*2,l,(l+r)/2);
build_segment_tree(i*2+1,(l+r)/2+1,r);
}
void update_segment_tree(int i) //線段樹單點更新(從下向上更新)
{
if(i==1) return;
int tmp = i/2;
tree[tmp].w = min(tree[tmp*2].w,tree[tmp*2+1].w);
update_segment_tree(tmp);
}
int query_segment_tree(int i,int l,int r) //線段樹查詢區間最小值
{
if(tree[i].l==l&&tree[i].r==r) return ans = min(tree[i].w,ans);
if(l<=tree[i*2].r)
{
//查詢區間全部在該節點的左子節點區間中
if(r<=tree[i*2].r) query_segment_tree(i*2,l,r);
//查詢區間包含該節點的左右子節點區間
else
{
query_segment_tree(i*2,l,tree[i*2].r);
query_segment_tree(i*2+1,tree[i*2+1].l,r);
}
}
//查詢區間全部在該節點的右子節點中
else query_segment_tree(i*2+1,l,r);
}
int main()
{
scanf("%d",&n);
build_segment_tree(1,1,n);
for(int i=1;i<=n;i++)
{
//葉子節點的最小值置爲它本身的值
scanf("%d",&tree[node_id[i]].w);
//從葉子節點向上更新線段樹
update_segment_tree(node_id[i]);
}
scanf("%d",&q);
while(q--)
{
scanf("%d%d%d",&a,&b,&c);
if(a==0)
{
ans = INF;
query_segment_tree(1,b,c);//查詢區間最小值
printf("%d\n",ans);
}
else
{
tree[node_id[b]].w = c;//動態修改節點最小值
update_segment_tree(node_id[b]);
}
}
return 0;
}