從簡單說起,線段樹其實可以理解成一種特殊的二叉樹。但是這種二叉樹較為平衡,和靜態二叉樹一樣,都是提前已經建立好的樹形結構。針對性強,所以效 率要高。這裡又想到了一句題外話:動態和靜態的差別。動態結構較為靈活,但是速度較慢;靜態結構節省記憶體,速度較快。
圖一就是一棵長度範圍為[1 , 10]的線段樹。
長度範圍為[1 , l] 的一棵線段樹的深度為log ( l - 1 ) + 1。這個顯然,而且儲存一棵線段樹的空間複雜度為o(l)。
線段樹支援最基本的操作為插入和刪除一條線段。下面已插入為例,詳細敘述,刪除類似。
將一條線段[a , b] 插入到代表線段[l , r]的結點p中,如果p不是元線段,那麼令mid=(l+r)/2。如果amid,那麼將線段[a , b] 也插入到p的右兒子結點中。
插入(刪除)操作的時間複雜度為o ( log n )。
上面的都是些基本的線段樹結構,但只有這些並不能做什麼,就好比乙個程式有輸入沒輸出,根本沒有任何用處。
最簡單的應用就是記錄線段有否被覆蓋,並隨時查詢當前被覆蓋線段的總長度。那麼此時可以在結點結構中加入乙個變數int count;代表當前結點代表的子樹中被覆蓋的線段長度和。這樣就要在插入(刪除)當中維護這個count值,於是當前的覆蓋總值就是根節點的count 值了。
另外也可以將count換成bool cover;支援查詢乙個結點或線段是否被覆蓋。
例題1(zju1610 count the colors 線段樹基本應用題目)
解析就這個題目而言,方法很多,而且資料範圍不大,但我們由線段樹的角度來解決這個問題。
建立一棵代表線段[0,8000]的線段樹,塗色操作就是將[a , b]塗成顏色c。最後做統計。
結構如下:
struct tnode
opt [1] = 0;
最後的答案就是opt [n]了,但是考慮時間複雜度,是o(m^2)級別的,m最大為500000,超時無疑。但是這裡我們看到了規劃的決策集合是一條連續的線段,是要在這條 線段上面取得最小值,那麼線段樹的結構就正好適合在這裡應用了。
由於這裡最小的單位是乙個點,所以我們採取線段樹的第一種變化,把元線段設定為單位點,即[k,k]。在規劃的時候維護線段樹即可。
線段樹結點結構中,需要加入的元素是int minstep 代表最少需要用到的覆蓋線段數目可以覆蓋到當前結點所代表的線段中。
例題3(pku2104k-th number)
給出乙個大小為n的陣列a,給出m個問題(1 <= n <= 100 000, 1 <= m <= 5 000)。問題格式為q(i,j,k),詢問從a[i]到a[j]第k大的元素是什麼。a中的數各不相同。
解析由於仍舊是離散的整數問題,我們依舊採取第一種變化。看到題目,最基本的想法就是排序然後求第k個數了,但是時限上不能滿足要求。
線段樹的最強大方面就是將一組數(一條線段)放到一起處理。每層樹需要的線段數目不會超過4,而深度為logn,所以最後操作的複雜度會是 o(logn)。
但是僅僅應用線段樹還是不夠的,即使我們知道了需要處理的線段是那些,但是由於線段過多,也無法準確求出第k個元素究竟是什麼。這裡二分策略就派上 了用場。我們用二分列舉第k個數字p,然後再在所要的線段中找到列舉的p所在的位置,同樣是用二分的策略。所以複雜度是 o(mlognlognlogn)。
我們在找p所在的位置的時候需要用到二分策略,也就是說,我們需要將線段所代表的結點排序,這裡可以將每一層的所有數放到一起,統一成乙個陣列 sortarray[depth]。其實也可以理解成將歸併排序的每個步驟記錄下來。
線段樹的第二種變化 (樹狀陣列)
在結構上對線段樹進行改變,可以得到線段樹的另一種變化。
用o(n)的一維陣列構造出線段樹,無其他附加空間。比方說,一棵從[0,l]的線段樹表示為tnode tree[l];
這裡應用二進位制將樹進行劃分。將整數r的二進位制表示中的最後乙個1換成0,得到數l。tree[r]代表的線段就是[l,r]。例如:6的二進位製表 示為(110)2將最後乙個1換成0即為(100)2=4,所以tree[6]代表的線段就是[4,6]。
析出數r的最後一位1的方法是:lowbit(r)=r^~r。
包含點l的一系列數為x1,x2,……,這裡x1=r,x2=x1+lowbit (x1),x3=x2+lowbit(x2),……
這種線段樹的優點在於:
1. 節省空間。完全線段長度的空間,無需左右指標,無需左右範圍。
2. 線段樹查詢嚴格log(r),因為二進位制的每位查詢一遍。
3. 狀態轉移快,操作簡單。
4. 擴充套件到多維較為容易。
缺點:1.隨意表示線段[a,b]較為困難。
這種線段樹適用於:
1. 查詢線段[0,l]的資訊。
2. 求線段[a,b]的和(應用部分和做差技術)。
// problem zju 1610
// segment tree
#define nocolor -1
#define mulcolor -2
#i nclude
#i nclude
int len;
struct tnode tree [16000] , *root = &tree [0];
int calcolor [8001] , many [8001];
void tnode :: construct ( int l , int r )
left = l; right = r;
if ( l + 1 == r )
int mid = ( l + r ) >> 1;
leftchild = &tree [len ++];
rightchild = &tree [len ++];
leftchild->construct( l , mid );
rightchild->construct( mid , r );
void tnode :: insert ( int l , int r , int c )
if ( col == c ) return;
if ( l == left && r == right )
int mid = ( left + right ) >> 1;
if ( col != mulcolor )
col = mulcolor;
if ( r <= mid )
if ( l >= mid )
leftchild -> insert ( l , mid , c );
rightchild -> insert ( mid , r , c );
void tnode :: calculate ()
if ( col != mulcolor && col != nocolor )
main ()
int total , a , b , c , i , t;
len = 1; tree [0].construct( 0 , 8000 );
// printf ( "after construct the tree , len = %d/n" , len );
while ( scanf ( "%d" , &total ) != eof ) {
tree [0].col = nocolor;
while ( total ) {
scanf ( "%d %d %d" , &a , &b , &c );
root -> insert( a , b , c );
total --;
memset ( calcolor , 0xff , sizeof ( calcolor ) );
memset ( many , 0 , sizeof ( many ));
root -> calculate ();
t = -1;
for ( i = 0; i <= 8000; i ++ ) {
if ( calcolor [i] == t ) continue;
t = calcolor [i];
if ( t != -1 ) many [t] ++;
for ( i = 0; i <= 8000; i ++ ) if ( many [i] )
printf ( "%d %d/n" , i , many [i] );
printf ( "/n" );
樹基礎知識
陣列儲存方式的分析 優點 通過下標方式訪問元素,速度快。對於有序陣列,還可使用二分查詢提高檢索速度。缺點 如果要檢索具體某個值,或者插入值 按一定順序 會整體移動,效率較低 示意圖 畫出操作示意圖 2.鏈式儲存方式的分析 優點 在一定程度上對陣列儲存方式有優化 比如 插入乙個數值節點,只需要將插入節...
樹鏈基礎知識
在一棵樹上進行路徑的修改 求極值 求和 乍一看只要線段樹就能輕鬆解決,實際上,僅憑線段樹是不能搞定它的。我們需要用到一種貌似高階的複雜演算法 樹鏈剖分。樹鏈,就是樹上的路徑。剖分,就是把路徑分類為 重鏈和輕鏈。記siz v 表示以v為根的子樹的節點數,dep v 表示v的深度 根深度為1 top v...
樹的基礎知識
樹 樹定義專業定義 1 有且只有乙個稱為根的節點 2 有若干個互不相交的子樹,這些子樹本身也是一棵樹 通俗定義 1 樹是由節點和邊組成 2 每個節點只有乙個父節點但可以有多個子節點 3 但有乙個節點例外,該節點沒有根節點,此節點稱為根節點 專業術語 節點父節點子節點 子孫堂兄弟 深度 從根節點到最底...