樹狀陣列學習

2021-08-15 13:47:42 字數 4009 閱讀 5488

我覺得,樹狀陣列挺重要的就是那個 lower( x ) = x & -x ,我說說我的理解吧。

每乙個正整數都可以拆分成 2 的某些冪之和,例如 15 = 8 + 4 + 2 + 1 , 6 = 4 + 2 , 7 = 4 + 2 + 1 (感覺可以解釋lca的倍增跳)。

那麼轉換成二進位制是什麼樣的呢?

15 (十進位制) = 1111 = 1000 + 100 + 10 + 1 

6  (十進位制) = 110   = 100 + 10

也就是前15 項可以由 8+4+2+1 項組成,1-8 , 9-12 , 13-14 , 15 

前 6 項可以由 4 + 2 項組成 , 1-4 , 5-6

樹狀陣列就是利用了這一點,計算前 15 項之和,可以先算出 1-8 , 9-12 , 13-14 , 15 ,這中間就需要乙個「拆」的過程,怎麼樣把 1111 的每乙個 1 都提取出來。

lower(x)  = x & -x 就可以。

根據位運算, -x 是把 x 的二進位制先取反再+1 , 

例如 15 = 1111,取反加一就是 0000+1 = 0001 ,1111 & 0001運算之後是 0001, 這第一步取出來的就是1111中最低位的 1 ,然後 15- lower( 15) = 14 ;

緊接著,對 14 進行同樣的操作,14 = 1110,取反+1就是 0001+1 = 0010 ,1110 & 0010 運算後是 0010,這次取出來的是1110中最低位的 1;

緊接著 14 -lower(14) = 12 ,對 12 操作,12 = 1100 , 取反+1就是 0011+1 = 0100 ,1100 & 0100運算之後就是0100 , 取出來的是 1100中的最低位的  1 ;

緊接著 12 - lower(12) = 4 ,對 4 操作 4 = 100 , 取反+1就是 011+1 = 100 , 100 & 100 = 100 ;

最後,4 - lower(4) = 0 ,  分解完全。

上面的步驟就是把 15  「湊成」  2 的某些冪之和,15 = 8 + 4 + 2 +1 , 然後 8 = 4 + 4 , 4 = 2 + 2 , 2 = 1 + 1 。

以上好像取反+1 就可以了,那 & 操作是什麼呢?

舉個例子,1010 ,取反+1就是  0101+1 = 0110 , 在這裡新生成了乙個高位0100,我們只要 0010 , 用&操作,1010&0110就是  0010 , 把新生成的高位消去。

總結一下,lower(x) = x & -x 究竟進行了啥操作——取反,就可以消掉這個數二進位制的高位的 1 ,例如 1100 , 就變成 0011 , 然後0011 + 1 ,就相當於是在恰好小於 0100 的 數 0011 上 +1 ,,同時,用& 操作消掉新生成的高位,就等於  0100, 神奇地把最低位的 1 剝出來了。我感覺,和前段時間學的非遞迴的,字典序的全排列很像,字典序是後面的部分已經達到了最大,就倒置過來,然後找下乙個數繼續開始。這裡 x & -x 也是差不多,把前面的  1 清空,後面0的部分取到最大,然後恰好+1 就可以取出原來最低位的 1。

所以,現在看這張圖是不是很好理解呢?

}這裡可以 x -= lower( x ) 很好理解了,就是拆。

那麼  x += lower( x ) , 就是一直加,直到 x >= n 停止,也許可以形成更高位,就像上圖中的 c[4] 和 c[8] ,可以走到更高的地方 c[8] , 歸 c[8] 「控制」 。6 二進位制  110 , 加上 lower(6) 就是 110 + 010 = 1000 , 這樣就可以傳遞到更高位,和「拆」是相反的操作。

一. 單點修改,區間查詢

最簡單的樹狀陣列,修改序列的乙個值,求區間和

樹狀陣列求逆序數 poj 2299

#include #include #include #include using namespace std ;

#define lower(x) x&-x

typedef long long ll ;

int c[500005] , n ;

struct node

} e[500005];

void update( int x )

}int sum( int x )

return ans ;

}int main()

sort( e+1 , e+n+1 ) ;

ll ans = 0 ;

for( int i = n ; i >= 1 ; --i )

cout << ans << endl ;

} return 0 ;

}

二. 區間修改,單點查詢:

在乙個區間內統一加上某個值,然後詢問某乙個點的大小。

這裡是區間求和的變形。

用乙個陣列 c 儲存 a[i] - a[i-1]

例如 c[1] = a[1] - a[0] , c[2] = a[2] - a[1] , c[3] = a[3] - a[2].....

然後把 c 陣列加起來,就是 a[n] - a[n-1] + ( a[n-1] - a[n-2] ) ...... = a[n] - a[0]

所謂的差分,其實就是把查詢每乙個點變成查詢區間和,通過相鄰之間的差距,求出第幾個數是多少

hdu 1556

#include using namespace std ;

#define ll long long

#define lower( x ) x&-x

int n , c[100005] , l , r , add ;

void update( int x , int add )

}ll sum( int x )

return ans ;

}int main()

for( int i = 1 ; i < n ; ++i )

printf( "%lld " , sum( i ) ) ;

printf( "%lld\n" , sum( n ) ) ;

} return 0 ;

}

三. 區間修改,區間查詢

這個也是利用了差分。

sum( n )

=a[1]+a[2]+a[3]+…+a[n-1]+a[n]

=c[1]+(c[1]+c[2])+…+(c[1]+c[2]+…+c[n])

=n*(c[1]+c[2]+…+c[n])-(0*c[1]+1*c[2]+2*c[3]+…+(n-1)*c[n]).

這裡就有乙個陣列,可以直接求和,那就是前面的 c[1]+c[2]+…+c[n],後面那一部分可以看作另外乙個陣列 

d[n] = (n-1)*c[n]

sum( n ) = n * 總和(c) - 總和(d)

這樣就等於是求和 (c) - 求和 (d)

#include #include #include using namespace std ;

#define lower( x ) x & -x

typedef long long ll ;

int n , q , l , r ;

ll c[100005] ;

ll d[100005] ;

void update( ll *c , int x , ll add )

}ll sum( ll *c , int x )

return ans ;

}ll solve( int x )

int main()

char command[2] ;

while( q-- )

else

} return 0 ;

}

今天對樹狀陣列學習的還不深,日後再總結。

樹狀陣列 從一維到二維

樹狀陣列學習

之前寫的題也遇到過用樹狀陣列,當時都是現查現學,而且總是搞不懂,今天又遇到了一道求區間和的題,不管最後是不是用樹狀陣列可以a,但是既然已經想到了這,就打算好好學習一下。可惜之前查到的資料都沒有儲存記錄,所以又重新查了些資料,彙總學習如下 文末附上樹狀陣列的詳細 樹狀陣列主要用到的操作 int low...

樹狀陣列學習

樹狀陣列與並查集類似,是一種資料結構,它可以用來維護字首和問題 不過,利用字首和和查分的思想,我們也可以用樹狀陣列解決區間問題 與線段樹不同,目前我暫且認為線段樹解決最值問題,而樹狀陣列解決求和問題 樹狀陣列原理建立在二叉樹上 利用lowbit運算實現向根節點儲存的原理 介紹lowbit的程式實現只...

樹狀陣列學習小結

樹狀陣列,又稱二進位制索引樹,英文名binary indexed tree。一 樹狀陣列的用途 主要用來求解數列的字首和,a 0 a 1 a n 由此引申出三模擬較常見問題 1 單點更新,區間求值。hdu1166 2 區間更新,單點求值。hdu1556 3 求逆序對。hdu2838 二 樹狀陣列的表...