史上講得最清楚的樹狀陣列(至少我是這麼認為的)

2022-04-30 14:42:07 字數 2305 閱讀 2823

出處:

是不是很像一顆樹?對,這就是為什麼叫樹狀陣列了~先看a圖,a陣列就是我們要維護和查詢的陣列,但是其實我們整個過程中根本用不到a陣列,你可以把它當作乙個擺設!c陣列才是我們全程關心和操縱的重心。先由圖來看看c陣列的規則,其中c8 = c4+c6+c7+a8,c6 = c5+a6……先不必糾結怎麼做到的,我們只要知道c陣列的大致規則即可,很容易知道c8表示a1~a8的和,但是c6卻是表示a5~a6的和,為什麼會產生這樣的區別的呢?或者說發明她的人為什麼這樣區別對待呢?答案是,這樣會使操作更簡單!看到這相信有些人就有些感覺了,為什麼複雜度被lg了呢?可以看到,c8可以看作a1~a8的左半邊和+右半邊和,而其中左半邊和是確定的c4,右半邊其實也是同樣的規則把a5~a8一分為二……繼續下去都是一分為二直到不能分,可以看看b圖。怎麼樣?是不是有點二分的味道了?對,說白了樹狀陣列就是巧妙的利用了二分,她並不神秘,關鍵是她的巧妙!

她又是怎樣做到不斷的一分為二呢?說這個之前我先說個叫lowbit的東西,lowbit(k)就是把k的二進位制的高位1全部清空,只留下最低位的1,比如10的二進位制是1010,則lowbit(k)=lowbit(1010)=0010(2進製),介於這個lowbit在下面會經常用到,這裡給乙個非常方便的實現方式,比較普遍的方法lowbit(k)=k&-k,這是位運算,我們知道乙個數加乙個負號是把這個數的二進位製取反+1,如-10的二進位制就是-1010=0101+1=0110,然後用1010&0110,答案就是0010了!明白了求解lowbit的方法就可以了,繼續下面。介於下面討論十進位制已經沒有意義(這個世界本來就是二進位制的,人非要主觀的構建乙個十進位制),下面所有的數沒有特別說明都當作二進位制。

上面那麼多文字說lowbit,還沒說它的用處呢,它就是為了聯絡a陣列和c陣列的!ck表示從ak開始往左連續求lowbit(k)個數的和,比如c[0110]=a[0110]+a[0101],就是從110開始計算了0010個數的和,因為lowbit(0110)=0010,可以看到其實只有低位的1起作用,因為很顯然可以寫出c[0010]=a[0010]+a[0001],這就為什麼我們任何數都只關心它的lowbit,因為高位不起作用(基於我們的二分規則它必須如此!),除非除了高位其餘位都是0,這時本身就是lowbit。

既然關係建立好了,看看如何實現a某乙個位置資料跟改的,她不會直接改的(開始就說了,a根本不存在),她每次改其實都要維護c陣列應有的性質,因為後面求和要用到。而維護也很簡單,比如更改了a[0011],我們接著要修改c[0011],c[0100],c[1000],這是很容易從圖上看出來的,但是你可能會問,他們之間有申明必然聯絡嗎?每次求解總不能總要拿圖來看吧?其實從0011——>0100——>1000的變化都是進行「去尾」操作,又是自己造的詞–」,我來解釋下,就是把尾部應該去掉的1都去掉轉而換到更高位的1,記住每次變換都要有乙個高位的1產生,所以0100是不能變換到0101的,因為沒有新的高位1產生,這個變換過程恰好是可以借助我們的lowbit進行的,k +=lowbit(k)。

好吧,現在更新的次序都有了,可能又會產生新的疑問了:為什麼它非要是這種關係啊?這就要追究到之前我們說c8可以看作a1~a8的左半邊和+右半邊和……的內容了,為什麼c[0011]會影響到c[0100]而不會影響到c[0101],這就是之前說的c[0100]的求解實際上是這樣分段的區間 c[0001]~c[0001] 和區間c[0011]~c[0011]的和,數字太小,可能這樣不太理解,在比如c[0100]會影響c[1000],為什麼呢?因為c[1000]可以看作0001~0100的和加上0101~1000的和,但是0101位置的數變化並會直接作用於c[1000],因為它的尾部1不能一下在跳兩級在產生兩次高位1,是通過c[0110]間接影響的,但是,c[0100]卻可以跳一級產生一次高位1。

可能上面說的你比較繞了,那麼此時你只需注意:c的構成性質(其實是分組性質)決定了c[0011]只會直接影響c[0100],而c[0100]只會直接影響[1000],而下表之間的關係恰好是也必須是k +=lowbit(k)。此時我們就是寫出跟新維護樹的**:

[cpp] view plain copy print?

void add(int k,int num) }

有了上面的基礎,說求和就比較簡單了。比如求0001~0110的和就直接c[0100]+c[0110],分析方法與上面的恰好逆過來,而且寫法也是逆過來的,具體就不累述了:

[cpp] view plain copy print?

int read(int k)//1~k的區間和

return sum;

} 三、總結一下吧

首先,明白樹狀陣列所白了是按照二分對陣列進行分組;維護和查詢都是o(lgn)的複雜度,複雜度取決於最壞的情況,也是o(lgn);lowbit這裡只是乙個技巧,關鍵在於明白c陣列的構成規律;分析的過程二進位制一定要深入人心,當作心目中的十進位制。

樹狀陣列維護區間最值

題目描述 給你乙個1 n的排列和乙個棧,入棧順序給定 你要在不打亂入棧順序的情況下,對陣列進行從大到小排序 當無法完全排序時,請輸出字典序最大的出棧序列 輸入描述 第一行乙個數n 第二行n個數,表示入棧的順序,用空格隔開,結尾無空格 輸出描述 輸出一行n個數表示答案,用空格隔開,結尾無空格 示例1輸...

樹狀陣列之區間最值

數學原理 利用上面的性質,在樹狀陣列的尾部插入資料,來建立乙個樹狀陣列 void push int pos void update int pos,int v int pre c pos pos lowbit pos 父親的位置 更新父親 while pos n 沒有更新父親 else break ...

樹狀陣列求區間最值

樹狀陣列 binary index tree 利用二進位制的一些性質巧妙的劃分區間,是一種程式設計,時間和空間上都十分理想的求區間和的演算法,同樣我們可以利用樹狀陣列優美的區間劃分方法來求乙個序列的最值 約定以 num 表示原陣列,以 idx 表示索引陣列,lowbit x x x 樹狀陣列求和時通...