1、前言
這么簡單的東西一直沒有來看一眼。。。因為最初學數據結構的時候就曾從各方各面了解到線段樹的各種優越性,各種比樹狀數組好,於是就看了線段樹就沒管了。。。但是樹狀數組的常數小,代碼短這些隱性優勢也許當時並不清楚吧。
2、概念
樹狀數組,依舊是一個線性數組構成,但是其性質卻如同樹結構一樣是立體的。如圖所示,我們首先給出一個最基本的a數組的一部分,大家可以觀察一下上方的c數組有什么規律?
我們將箭頭看做該節點的兒子節點,每一個節點的權值為所有兒子節點的權值和,易得:
c[1]=a[1];c[2]=a[1]+a[2];c[3]=a[3];c[4]=c[2]+a[3]+a[4]=a[1]+a[2]+a[3]+a[4];
c[5]=a[5];c[6]=a[5]+a[6];c[7]=a[7];c[8]=c[4]+c[6]+a[7]=a[1]+a[2]+...+a[8]。
沒錯,上方的c數組就是樹狀數組。分析數據之后,我們可以得到樹狀數組的一些性質:對於c[i],他的兒子節點取決於i的所有因子中最多有2^j次冪,則向前取2^j個數作為兒子,即[i-2^j+1,i]。例如,6的最大2次方因子為2,即2^1,則向前取2個數,則c[6]=a[5]+a[6];8的最大2次方因子為8,即2^3,則向前取8個數,則c[8]=a[1]+a[2]+...+a[8]。
3、構建&詢問&修改
三個步驟,一起講了吧,都很簡單。
<1>構建:應該並不存在什么難度,唯一要考慮的就是如何得到為該數因子中最大的2^x是多少。我自己在想的時候只能想到很復雜的還要預處理的方式,其實巧妙地利用位運算符就可以以O(1)的速度直接得到,至於原因,沒有去考慮,記着就行吧。代碼:
------------------------------------------------------------------------------------------------------
int lowbit(int x) { return x&(-x); }
------------------------------------------------------------------------------------------------------
<2>求數組的和:我們注意到,樹狀數組在求和的時候,相對於普通數組的優勢就像樹鏈剖分和普通LCA一樣。每次我們不需要一個一個相加,直接利用當前位置的lowbit值跳轉即可,如代碼:
------------------------------------------------------------------------------------------------------
int getSum(int now)
{
int sum=0;
while (now>0) sum+=c[now],now-=lowbit(now);
return sum;
}
------------------------------------------------------------------------------------------------------
<3>單點修改權值:同樣地,修改也是非常快的,O(log n),假設當前為節點i加上val,如代碼:
------------------------------------------------------------------------------------------------------
int update(int i,int val) { while (i<=n) c[i]+=x,i+=lowbit(i); }
------------------------------------------------------------------------------------------------------
4、總結
顯然可以看出來了,樹狀數組空間復雜度小些,代碼短些,常數也小些,這些平時可能不是很注意的優勢都要注意,雖然它的功能缺失遜於線段樹,但是在能夠使用樹狀數組的情況下,我覺得還是可以多用用。
UPDATE(20151009):
因為個人覺得逆序對一個東西不需要單獨放出來,又其可以用樹狀數組去求解,故就在這里寫寫吧。見下。
1、概念
逆序對僅在數組中有意義,對於一個包含n個非負整數的數組a,如果有i<j,且a[i]>a[j],則稱(a[i],A[j])為數組a中的一個逆序對。其實我好幾次看這個的時候都覺得它用途應該很小,也不知道能在什么情況下用。直到看過了NOIP的火柴排隊之后就能夠理解了,而后今天又做了一道類似的題目。
2、求法
逆序對有兩種求法,O(n^2)的做法應該誰都想得到吧?直接暴力查找就行了。而O(n log n)算法是利用樹狀數組或者歸並排序來維護。從代碼復雜度而言,樹狀數組顯然是首選。為什么?因為在求逆序對的過程中,我們唯一需要知道的數組內元素之間的關系就是誰大誰小,那么我們將元素一一放入樹狀數組中,可以在O(log n)的時間內求出比它小(大)的數。
代碼:
-----------------------------------------------------------------------------------------------------
int n,a[MAXN],b[MAXN],ans=INF,o;
int lowbit(int o) { return o&-o; }
int query(int o) { int res=0; while (o) res+=b[o],o-=lowbit(o); return res; }
void insert(int o) { while (o<=n) b[o]++,o+=lowbit(o); }
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=n;i>=1;i--) o+=query(a[i]-1),insert(a[i]);
-----------------------------------------------------------------------------------------------------