在解題過程中,我們有時需要維護乙個陣列的字首和s[i]=a[1]+a[2]+...+a[i]。
但是不難發現,如果我們修改了任意乙個a[i],s[i]、s[i+1]...s[n]都會發生變化。
可以說,每次修改a[i]後,調整字首和s在最壞情況下會需要o(n)的時間。
當n非常大時,程式會執行得非常緩慢。
因此,這裡我們引入「樹狀陣列」,它的修改與求和都是o(logn)的,效率非常高。
為了對樹狀陣列有個形象的認識,我們先看下面這張圖。
如圖所示,紅色矩形表示的陣列c就是我們所維護的樹狀陣列。
該影象的特徵:構成了乙個以c[8]為根結點,其他c[m]、a[n]為子結點的樹。
對於陣列a下標為奇數時,其上都僅僅只有乙個c元素,即c[1] = a[1]、c[3] = a[3]、c[5] = a[5]等等
對於陣列a下標為偶數時,這是一系列a[i]的和。
c[1] = a[1];c[1]的管轄範圍為1
c[2] = a[2] + a[1] ;c[2]的管轄範圍為2
c[3] = a[3];c[3]的管轄範圍為1
c[4] = a[4] + a[3] + a[2] + a[1] ;c[4]的管轄範圍為4
c[5] = a[5];c[5]的管轄範圍為1
c[6] = a[6] + a[5] ;c[6]的管轄範圍為2
c[7] = a[7];c[7]的管轄範圍為1
c[8] = a[8] + a[7] + a[6] + a[5] + a[4] + a[3] + a[2] + a[1] ;c[8]的管轄範圍為8
這裡的管轄範圍就是說c由幾個a組成。
由上可得(s由c來表示):
s[1] = c[1]
s[2] = c[2]
s[3] = c[3] + c[2]
s[4] = c[4]
s[5] = c[5] + c[4]
s[6] = c[6] + c[4]
s[7] = c[7] + c[6] + c[4]
s[8] = c[8]
通過觀察影象:s[i]表示為i之前所有最大子樹的根結點c累加的和
上面所說的管轄範圍就是下面列出來的2^k所表示的數
i
二進位制
2^k(k:i的二進位制的末尾0的個數) 101
12102
31114
1004
510116
1102
711118
1000
82^k的求法:i 的二進位制去掉所有高位的1,只保留最低位的1,如果只存在乙個最高位的1,則 2^k 就是它本身。
如1(1),2(10),4(100),8(1000),16(10000)。即都是2的冪。
所以,當我們要求2的冪是只要i等於2^k就可以了,而2^k由位運算很容易求得。
對於c[i]如何求得他的父節點?
由上**推斷,c[i]的父節點為c[i+2^k],也就是說將 i 的二進位制的最末尾的1去掉,相鄰的高位+1
對於c[i]如何求得他的下乙個節點?
由上**推斷,c[i]的下乙個節點是c[i-2^k],也就是說將i的二進位制最末尾的1去掉。
由上:c[i]表示a[i-2^k+1]到a[i]的和,而k則是i在二進位制時末尾0的個數。
利用位運算,我們可以直接計算出2^k=i&(i^(i-1)) 也就是i&(-i),所以c[i]的父節點為c[i + i & (-i)]。
這個k(i的二進位制末尾0的個數)就是該節點在樹中的高度,因而這個樹的高度不會超過logn。
所以,當我們修改a[i]的值時,可以從c[i]往根節點一路上溯,調整這條路上的所有c即可,這個操作的複雜度在最壞情況下就是樹的高度即o(logn)。
對於求數列的前n項和s[i],只需找到n以前的所有最大子樹,把其根節點的c加起來即可。
不難發現,這些子樹的數目是n在二進位制時1的個數。因此,求和操作的複雜度也是o(logn)。
在最後,我們將給出一些樹狀陣列的實現**,希望讀者能夠仔細體會其中的細節。
求最小冪2^k:
[cpp]view plain
copy
intlowbit(
intt)
求前n項和:
[cpp]view plain
copy
intsum(
intend)
return
sum;
}
對某個元素進行加法操作:
[cpp]view plain
copy
void
plus(
intpos ,
intnum)
}
參考:
演算法學習筆記 樹狀陣列
樹狀陣列 binary indexed trees 是一種可以支援單點修改,較快維護字首和的資料結構。他的實現方式是用乙個陣列維護乙個 樹狀 的結構 如下圖所示 記錄一些區間的區間和,實現快速計算字首和。能看到這裡的同學應該已經對字首和不陌生了。本片部落格就不再贅述 lowbit 操作是表達二進位制...
演算法學習筆記 樹狀陣列 線段樹
支援單點修改和區間查詢兩個操作,單次操作o logn tr x 陣列存的是原陣列在區間 x lowbit x x 上的和 若要將乙個數x變為v,則將x x v,即加上v x 模板如下 建立樹狀陣列 for int i 1 i n i scanf d a i for int i 1 i n i add...
樹狀陣列 小白
樹狀陣列 bit 是一種利用樹的2進製特徵進行檢索的樹狀結構。樹狀結構是一種奇妙的資料結構,不僅非常高效,而且 十分簡潔 比線段樹的 要短且更易理解,但是可以解決的問題也是有限的,沒有線段樹那麼廣泛 樹狀陣列就是用來 動態的求字首和 的時間複雜度在log n之內 一般來說就是兩個操作 1.單點修改 ...