樹狀數組(Binary Index Tree)利用二進制的一些性質巧妙的劃分區間,是一種編程,時間和空間上都十分理想的求區間和的算法,同樣我們可以利用樹狀數組優美的區間劃分方法來求一個序列的最值
約定以 num[] 表示原數組, 以 idx[] 表示索引數組, Lowbit(x)=x&(-x)
樹狀數組求和時通過構造數組 idx[] 使 idx[k]=sum(num[tk]), tk [k-Lowbit(k)+1,k], 使用同樣的方法構造最值索引數組:
以最大值爲例, 先討論詢問過程中不對數組做任何修改的情況, 用 idx[k] 記錄 [k-Lowbit(k)+1,k] 區間內的最大值, 可以仿照求和時的方法得到:
void Init(int n){
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j+=Lowbit(j)){
idx[j]=MAX(idx[j],num[i]);
}
}
}
這種方法在每次調用該函數前都必須對數組進行初始化, 這樣對於數據範圍比較大的時候不是很優美, 這樣我們可以改爲:
void Init(int n){ for(int i=1;i<=n;i++){ idx[i]=num[i]; for(int j=1;j<Lowbit(i);j<<=1){ idx[i]=MAX(idx[i],idx[i-j]); } } }
這樣, 在更新到第k個數時, 所有 t(t<k) 都已經是正確的值了, 不存在上面那個函數的情況了
然後再來看查詢的問題, 對於區間 [l,r] 把該區間轉化爲多個的小區間再進行求最值, 方法是從後往前對每一個索引數的範圍進行判斷, 如在進行到第k項時,該數控制的範圍是 [k-Lowbit(k)+1,k], 如果k-Lowbit(k)+1在所求的範圍內的話則將該區間的最值加入最值的判斷,然後轉至地k-Lowbit(k),否則的話就只對第k個數進行最值判斷,然後轉至k-1,具體實現如下:
int Query(int l,int r){
int ans=num[r];
while(true){
ans=MAX(ans,num[r]);
if(r==l) break;
for(r-=1;r-l>=Lowbit(r);r-=Lowbit(r)){
ans=MAX(ans,idx[r]);
}
}
return ans;
}
該查詢的複雜度爲log(n)
st算法的複雜度 O(nlog(n)) / O(1) , 線段樹爲 O(nlog(n)) / (log(n)),樹狀數組 O(<nlog(n)) / O(log(n))
空間複雜度 st 爲 O(nlog(n)), 線段樹 O(n),常數較大 , 樹狀數組是 O(n)
編程上 st 和 樹狀數組 都比較容易實現,線段樹代碼較長
另外線段樹靈活性較大
PKU 3264 題:
st Memory: 6372K Time: 1250MS 964B
BIT Memory: 716K Time: 1282MS 933B
SegTree 未測,要比st更大
然後我們可以進一步擴展到邊查詢邊修改的情況
每次直接去更新父親節點自然是不行的, 爲了維護索引數組的正確性,我們在對每個父親節點進行更新時都要查詢他的所有兒子節點,在其中取最優值, 得到代碼如下:
void Modify(int p,int v,int n){
num[p]=v;
for(int i=p;i<=n;i+=Lowbit(i)){
idx[i]=MAX(idx[i],v);
for(int j=1;j<Lowbit(i);j<<=1){
idx[i]=MAX(idx[i],idx[i-j]);
}
}
}
複雜度爲 O(log^2(n)), HDU 1754 I hate it 437MS 1776K
另外,該方法還有一個減枝, 很容易想到,在求最大值時,當某個值更新的值大於原值的時候是沒有必要再去查詢兒子節點的,所以內部循環可加一個判斷來判定是否需要掃描兒子節點,可能是數據問題,該題時間並沒有大的變化
轉自:http://www.cnblogs.com/ambition/archive/2011/04/06/bit_rmq.html