樹狀數組專題
基礎和原理就不多說了,做了一些樹狀數組的題,現在根據題目來大概分一下類。
1、一維 單點修改 區間查詢
這是樹狀數組最基礎的用法,query函數的判斷條件如果習慣寫成while(x)的請注意一下,如果x爲負數的話,會造成死循環。要將數據偏移到1下標開始。
void add(int x,int k)
{
while(x<=n)
{
sum[x]+=k;
x+=lowbit(x);
}
}
query(int x)
{
int ans = 0;
while(x>0)
{
ans+=sum[x];
x-=lowbit(x);
}
return ans;
}
2、一維 區間修改 單點查詢
操作是修改一個區間內的數,查詢是隻查詢一個點。
如果區間範圍很大,我們每次模擬修改一遍是非常耗時的。所以我們要優化每次對區間的操作,如果對區間[3,5]操作一次,x[3]++,x[6]--。
那麼我們觀察前六個數的前綴和爲:0 0 1 1 1 0 ,前綴和的狀態正好可以表示我們剛纔的操作,而且去更新前綴和可以藉助樹狀數組優化至logn。
因此一個位置的前綴和就可以表示這個位置的值。
修改:[x,y] + c
add(x,c) add(y+1,-c)
查詢:query(x)
題目:hdu 1556題解:http://blog.csdn.net/chy20142109/article/details/50674183
3、一維 區間修改 區間查詢
如果對一個區間[x,y]都加上c
那麼如果查詢前i個數的和:s(i) 表示改變之前的和 s(i)'表示改變之後的和
(1) i>y s(i)’ = s(i) + ( y - x + 1 ) * c
(2) i∈[x,y] s(i)’ = s(i) + ( i - x + 1 ) * c
對於區間修改,我們可以參考第二種,在x位置加上c,y+1位置減去c,這樣求得的前綴和的值就只在[x,y]內有效。觀察(1)(2)都有一個固定的 (1-x)*c,所以我們可以在x位置先將其減去,再在y+1處加上y*c,用意爲,如果範圍超過y的話,這個前綴和正好就是( y - x + 1 ) * c。但是如果沒超過怎麼辦?如果在範圍內的話c還是有效的,所以加上i*c即可。
這樣我們設計兩個前綴和來維護。
第一個T1來維護增加量c
第二個T2來維護區間的實際增加量
所以每次操作[x,y]都加上一個值c就可以
T1:add(x,c) add(y+1,-c)
T2:add( x , (1-x)*c ) add( y+1, y*c )
查詢[1,x]
T2.query(x) + x * T1.query(x)
如果查詢[x,y],那麼就先用查詢y的結果減去查詢x-1的結果即可。
題目:poj 3468題解:http://blog.csdn.net/chy20142109/article/details/50674218
4、二維 單點修改 區間查詢
有一個二維矩陣,每次修改一個點,或者查詢一個矩形範圍內的和。
void add(int x,int y,int k)
{
while( x <= n )
{
int ty = y;
while( ty <= n )
{
sum[x][ty]+=k;
ty+=lowbit(ty);
}
x+=lowbit(x);
}
}
int query(int x,int y)
{
int ans = 0;
while( x>0 )
{
int ty = y;
while(ty>0)
{
ans+=sum[x][ty];
ty-=lowbit(ty);
}
x-=lowbit(x);
}
return ans;
}
我看到有人用for來寫,代碼也很簡潔。
void add(int x,int y,int k)
{
for(int i = x; i <= n; i+=lowbit(i))
for(int j = y; j <= n; j+=lowbit(j))
sum[i][j]+=k;
}
int query(int x,int y)
{
int ans = 0;
for(int i = x; i > 0; i-=lowbit(i))
for(int j = y; j > 0; j-=lowbit(j))
ans+=sum[i][j];
return ans;
}
二維樹狀數組也是維護一個前綴和,只不過一個增加了一維而已。
回到問題,如果是單點修改的話直接add(x,y,c)即可。
如果是區間查詢的話,調用一次query(x,y)顯然是求(1,1) 到 (x,y)範圍內矩形的和。觀察下圖,我們沒辦法一次查詢出來給定區間的值,但是我們可以通過計算得出。
現在如果要查詢藍色範圍內的和
調用A點是求紫色邊框
調用B點是求綠色邊框
調用C點是求黃色邊框
調用D點是求紅色邊框
那麼A-B-C+D即是答案。
題目:poj 1195
題解:http://blog.csdn.net/chy20142109/article/details/50674194
5、二維 區間修改 單點查詢
一次修改一個區間,或者查詢一個點的值。
這個可以仿照一維情況下1->2的變化,因爲要照顧區間修改,所以用一個點的前綴和來表示這個點的變化。我們確定起點和終點,在起點設置影響,那麼將會從起點開始一直擴散到結束,所以我們在區間結束處做一次相反的操作來抵消區間終點之後造成的影響。
來看這個圖,我們想對藍色區間內修改一次,那麼如果我們改變第一個藍色的點會造成紫色區間內的修改,所以我們必須再通過修改來抵消這些多餘的影響。
於是就有了黃色、綠色、紅色的框...具體怎麼寫大家可以試着想一想。
題目:poj 2155
題解:http://blog.csdn.net/chy20142109/article/details/50674204
6、求逆序對
逆序對就是 i < j 並且 a[i] > a[j],這樣的存在算一對
我們可以將一個序列倒着考慮,如果處理到第i個數a[i],先查詢一下之前已經有多少個數比a[i]小,然後再將a[i]加進去。我們用樹狀數組來維護數的數量,就可以查詢1~x範圍內的數有多少個了。
題目:poj 2299 這道題是樹狀數組求逆序對模板,可以根據理解試着寫一下題目:poj 3067
題解:http://blog.csdn.net/chy20142109/article/details/50660162
還有一些問題也是藉助樹狀數組統計來優化計算的。可以自己練習一下,樹狀數組用法比較活,代碼也很少,是一個不錯的數據結構。
1、 hdu 1541
題解:http://blog.csdn.net/chy20142109/article/details/50660192
2、 poj 2481
題解:http://blog.csdn.net/chy20142109/article/details/50660168
3、 poj 2182
題解:http://blog.csdn.net/chy20142109/article/details/50660177
4、 poj 1990
題解:http://blog.csdn.net/chy20142109/article/details/50673968
5、poj 2309
題解:http://blog.csdn.net/chy20142109/article/details/50674157
6、poj 3321
題解:http://blog.csdn.net/chy20142109/article/details/50674049