關於線段樹基礎
阿新 • • 發佈:2022-05-24
首先明白什麼是線段樹:
線段樹是一棵二叉樹,每個節點表示序列上的一段區間,其中根節點表示區間[1,n]
從根節點開始,只要區間長度不為1,就將區間劃分為兩半,並分給兩個子結點
如下圖,就是n=8的線段樹:
當節點表示區間[l,r],當l≠r時,左孩子表示[l,(l+r)/2],右孩子表示[(l+r)/2+1,r]
當l=r時,該節點為葉子節點
線段樹的基本性質:
性質1.總節點數為2n-1
性質2.線段樹並不一定是一棵完全二叉樹,最後一層可能為空,且空結點的個數可能能達到2n個,因此最好開一棵4n的陣列來避免LE
性質3.層數約為,即每個葉節點到根節點的距離約為個節點組合而成性質
性質4.任何區間都可以用不超過2 個結點組成
線段樹工作原理:
我們先線上段樹的每個節點都儲存該區間的總和。假設起始都為0
對於操作1,令A[x]加上y,我們先從根節點出發,找到[x,x],比如x=3
此時,路徑上每個區間都包含x
接下來,將這些區間儲存的總和都加上y,比如y=4
這就是單點修改的工作原理,跟樹狀陣列有些相似之處,即修改一個點會對其祖先有同樣效果的影響,即要將其所有的祖先結點都通過修改的方式加上同樣的效果來保證影響的後效性。
問題2:詢問區間[x,y]的總和
就是尋找這個區間包含的最長且完整的、沒有交集的若干區間。
下面是線段樹的模板:
1、建樹:
1 struct Tree{ 2 intw,l,r; 3 //分別代表此節點的區間所有元素的和,其左兒子區間的和右兒子區間的和 4 }tree[1001]; 5 6 void build(int l,int r,int k) 7 { 8 tree[k].l=l; 9 tree[k].r=r; 10 if(l==r)//如果左邊界和右邊界相等,它就是葉節點,直接給他賦值 11 { 12 scanf("%lld",&tree[k].w); 13 return; 14 } 15 int mid=(l+r)/2; 16 build(l,mid,k*2); 17 build(mid+1,r,k*2+1); 18 tree[k].w=tree[k*2].w+tree[k*2+1].w; 19 //假設父節點的編號是k,那麼左兒子的編號就是2k,右兒子的編號就是2k+1; 20 //父節點代表的區間元素總和就等於左兒子和右兒子相加之和 21 }
2.單點查詢
1 int query(int k) 2 { 3 int l=tree[k].l,r=tree[k].r; 4 if(l==r)return tree[k].w; 5 int mid=(l+r)/2; 6 if(x<=mid)return query(k*2); 7 return query(k*2+1); 8 }
之所以不用O(n)的暴力演算法,是因為