我覺得,樹狀陣列挺重要的就是那個 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 二 樹狀陣列的表...