如果給你乙個陣列,讓你求某個區間的和,你很自然會想到遍歷一遍陣列,複雜度是o(n),但是如果有多次詢問呢,你也許會想到用字首陣列,通過o(n)的預處理,達到o(1)的查詢,但是如果要更新某個元素的值呢,如果用字首和的思想,每更新乙個元素就會更新字首陣列,每次複雜度是o(n),如果有n次更改,複雜度為o(n^2)。有沒有更快的呢,這時候樹狀陣列就排上用場了,樹狀陣列可以用來解決動態陣列連續和的問題。
樹狀陣列(binary indexed tree(b.i.t), fenwick tree):是乙個查詢和修改複雜度都為log(n)的資料結構。主要用於查詢任意兩位之間的所有元素之和,但是每次只能修改乙個元素的值;經過簡單修改可以在log(n)的複雜度下進行範圍修改,但是這時只能查詢其中乙個元素的值(如果加入多個輔助陣列則可以實現區間修改與區間查詢)。
樹狀陣列大致就長下面這個模樣。
那麼樹狀陣列到底是怎麼把修改和求和都做到o(log(n))呢
當我麼求和的時候,c[1]=a[1],c[2]=c[1]+a[2],c[4]=c[2]+c[3]+a[4],當我們更新a[2]的時候,我們只需要更新c[2],c[4],c[8],所以相當與每層我們只操作乙個結點,即o(log(n))。
當我們更新的時候c[i]的時候,就從c[i]開始往右走,邊走邊往上爬。比如我們更新c[2]的時候,我們先更新c[2],然後更新c[4]、c[8]、c[16]。
當我們要求和的時候,我們從c[i]開始往左走,邊走邊往上爬。比如我們求前7項和,先找到c[7],然後加上c[6],最後加上c[4]。
那我們怎麼才能做到這種奇怪的走法呢,
圖中灰色結點表示c[i],白色結點表示以i結尾的和,很神奇,灰色結點c[4]=a[1]+a[2]+a[3]+a[4]表示以4結尾的和,我們很好理解,但是為什麼灰色結點c[6]表示的表示的只有a[5]+a[6] 我們怎麼來控制個數呢,對於c[4],我們相當於從下標4開始,往前數了4個數,對於c[6],我們從下標6開始往前數兩個數,有個公式c[i]=a[i-2^k+1]+a[i-2^k+2]......+a[i],其中k表示i的二進位制末尾0的個數
i2進製
k下標取值範圍
10001
0i-2^k+1...i=1...1
c[1]=a[1]
20010
1i-2^k+1...i=1...2
c[2]=a[1]+a[2]=c[1]+a[2]
30011
0i-2^k+1...i=3...3
c[3]=a[3]
40100
2i-2^k+1...i=1...4
c[4]=a[1]+a[2]+a[3]+a[4]=c[2]+c[3]+a[4]
50101
0i-2^k+1...i=5...5
c[5]=a[5]
60110
1i-2^k+1...i=5...6
c[6]=a[5]+a[6]=c[5]+a[6]
所以我們求和的時候我們就先找i,然後往前數2^k個數,既然k表示i的二進位制末尾0的個數,其實就是前一位對應的權值,比如4的二進位制為0100,末尾有2個0,所以2^k=4,從後往前數第一位不為0的出現在第三位,權值為100,即4。
那我們我們怎麼計算這個權值呢,lowbit(i)=2^k=x&-x,先給出**
int lowbit(int x)
還是以4為栗子:lowbit(4)=4,4二進位制為0100,-4的二進位制為4的二進位制按位取反然後加1,-4=1100,所以4&(-4)=0100 即為4;
所以c[i]等於以i開始從後往前數i&(-i)個數。
當我們更新c[i]的時候,就從c[i]開始往右走,邊走邊往上爬。所以i逐漸變大但小於等於n,即更新時的下標變化:i+=lowbit(i)
當我們要求和的時候,我們從c[i]開始往左走,邊走邊往上爬。所以i逐漸變小但大於0,即求和時下標變化:i-=lowbit(i)
預處理方法:先把a和c陣列都清空,然後執行n次add操作,所以時間複雜度為o(nlog(n))。
樹狀陣列主要是用來維護動態陣列連續和問題。
hdu 1166 敵兵布陣
題意:有n個營地,每個營地開始都有一定數量的敵軍,然後有下面三種命令
(1) add i j,i和j為正整數,表示第i個營地增加j個人(j不超過30)
(2)sub i j ,i和j為正整數,表示第i個營地減少j個人(j不超過30);
(3)query i j ,i和j為正整數,i<=j,表示詢問第i到第j個營地的總人數
題解:由於n=50000比較大,而且命令多達40000,所以可以用樹狀陣列來解決,當然也可以用線段樹來維護區間和,只不過線段樹**量比較大,所以對於這種單點更新求區間和問題最好使用樹狀陣列,寫起來節約時間,也不容易出錯。下面分別給出樹狀陣列和線段樹的**。
#include#includeusing namespace std;
const int maxn=50000+7;
int tree[maxn<<1]; //樹狀陣列
int n;
char s[10];
int lowbit(int x)
void add(int x,int d)
return ;
}int sum(int x)
return s;
}int main()
printf("case %d:\n",t);
int x,y;
while(scanf("%s",s)==1&&strcmp(s,"end"))
else if(!strcmp(s,"add"))
else }}
return 0;
}
#include#includeusing namespace std;
const int maxn =1e5+7;
long long int segtree[maxn<<2],a[maxn];
int n,x,y;
char s[10];
void pushup(int now) //維護的資訊 維護區間和
void build_tree(int l,int r,int now)
int mid=(l+r)>>1;
build_tree(l,mid,now<<1);
build_tree(mid+1,r,(now<<1)|1);
pushup(now);
}long long int query(int ql,int qr,int l,int r,int now)
long long int ans=0;
int mid=(l+r)>>1;
if(ql<=mid)ans+=query(ql,qr,l,mid,now<<1);
if(qr>mid)ans+=query(ql,qr,mid+1,r,(now<<1)|1);
return ans;
}void update(int pos ,int c,int l,int r,int now)
int mid=(l+r)>>1;
if(pos<=mid)update(pos,c,l,mid,now<<1);
else update(pos,c,mid+1,r,(now<<1)|1);
pushup(now);
}int main()
else if(!strcmp(s,"add"))
else }}
return 0;
}
HDU 1166 敵兵布陣 樹狀陣列
用樹狀陣列很簡單,太晚了,貼下 睡覺去。另,研究線段樹的時候,發現網上流傳著有幾種不同的線段樹,最正宗的是以單位區間為單位,只能處理線段 另外還有幾種葉子結點是點的,這種也可以用來處理點,所以這題是可以用這種線段樹做的。還搞不太清楚它們之間的關係。mark一下,明天再說。include includ...
HDU 1166 敵兵布陣 樹狀陣列
problem description c國的死對頭a國這段時間正在進行軍事演習,所以c國間諜頭子derek和他手下tidy又開始忙乎了。a國在海岸線沿直線布置了n個工兵營地,derek和tidy的任務就是要監視這些工兵營地的活動情況。由於採取了某種先進的監測手段,所以每個工兵營地的人數c國都掌握的...
HDU 1166 敵兵布陣 (樹狀陣列)
敵兵布陣 time limit 1000msmemory limit 32768kb64bit io format i64d i64u submit status description c國的死對頭a國這段時間正在進行軍事演習,所以c國間諜頭子derek和他手下tidy又開始忙乎了。a國在海岸線沿...