標籤:通過 tar view ref play 樹狀數組 下標 資料結構 .com
感謝http://www.cnblogs.com/xudong-bupt/p/3484080.html
樹狀數組(BIT)是能夠完成下述操作的資料結構:
給定一初始值全為零的數列a1,a2a,a3...,an
1.給定i,計算a1+a2+...+ai
2.給定i和x,執行ai+=x
BIT的結構:
數組bit[N]
bit[1]=C1=A1;bit[2]=C2=C1+A2;
BIT就是使用數組來維護上面所說的部分和
以1結尾的1,3,5,7長度為1
以1個0結尾的2,6長度為2
以兩個0結尾的4長度為4
這樣,編號的二進位就能和區間對應起來
通過這個性質,實現上述功能
修改操作 :
修改a[k]後,c數組的哪些元素受到影響?
p1=k肯定受到影響, 設pi的父親為p(i+1),則
p(i+1)=pi+2^L,L為pi二進位中末尾0的個數
給a[3]增加x,則p1=3, p2=3+20=4, p3=4+22=8, p4=8+23=16>9,因此修改c[3],c[4],c[8]
它所有的後繼(祖先)受影響
找後繼
求和操作 :
設要求前k項的和,則從最後一項開始 k1=k
k(i+1)=ki-2^s,
s為ki二進位末尾0的個數 k=7,
則k1=7, k2=k1-20=6, k3=k2-21=4, k4=k3- 22=0,
因此前7項和為c[7]+c[6]+c[4]
1) a[]: 儲存未經處理資料的數組。(操作(1)求其中連續多個數的和,操作(2)任意修改其中一個元素)
e[]: 樹狀數組,其中的任意一個元素e[i]可能是一個或者多個a數組中元素的和。如e[2]=a[1]+a[2]; e[3]=a[3]; e[4]=a[1]+a[2]+a[3]+a[4]。
2) e[i]是幾個a數組中的元素的和?
如果數字 i 的二進位表示中末尾有k個連續的0,則e[i]是a數組中2^k個元素的和,則e[i]=a[i-2^k+1]+a[i-2^k+2]+...+a[i-1]+a[i]。
如:4=100(2) e[4]=a[1]+a[2]+a[3]+a[4];
6=110(2) e[6]=a[5]+a[6]
7=111(2) e[7]=a[7]
3) 後繼:可以理解為節點的父親節點。是離它最近的,且編號末位連續0比它多的就是它的父親,如e[2]是e[1]的後繼;e[4]是e[2]的後繼。
如e[4] = e[2]+e[3]+a[4] = a[1]+a[2]+a[3]+a[4] ,e[2]、e[3]的後繼就是e[4]。
後繼主要是用來計算e數組,將當前已經計算出的e[i]添加到他們後繼中。
前驅:節點前驅的編號即為比自己小的,最近的,最末連續0比自己多的節點。如e[7]的前驅是e[6],e[6]的前驅是e[4]。
前驅主要是在計算連續和時,避免重複添加元素。
如:Sum(7)=a[1]+...+a[7]=e[7]+e[6]+e[4]。(e[7]的前驅是e[6], e[6]的前驅是e[4])
計算前驅與後繼:
lowbit(i) = ( (i-1) ^ i) & i ;
節點e[i]的前驅為 e[ i - lowbit(i) ];
節點e[i]的後繼為 e[ i + lowbit(i) ]
核心思想:
(1)樹狀數組中的每個元素是原數組中一個或者多個連續元素的和。
(2)在進行連續求和操作a[1]+...+a[n]時,只需要將樹狀數組中某幾個元素的和即可。時間複雜度為O(lgn)
(3)在進行修改某個元素a[i]時,只需要修改樹狀數組中某幾個元素的和即可。時間複雜度為O(lgn)
一維樹狀數組常用的3個函數
int lowbit(int x) //取x的最低位1,比如4,則返回4,如5,則返回1{ return x&(-x);}void update(int i, int val) //將第i個元素增加val{ //i的祖先都要增加val while(i <= n) { sum[i] += val; i += lowbit(i); //將i的二進位未位補位得到其祖先 }}int Sum(int i) //求前i項的和{ int s = 0; //將前i項分段 while(i > 0) { s += sum[i]; i -= lowbit(i); //去掉i的二進位最後一個 } return s;}
準系統
#include<iostream>#include<cstring>#include<cstdio>using namespace std;char order[10];int a[50010];inline int lowbit(int k){ return k&-k;}inline void update(int i,int N,int plus)///界限N,為第i項加上plus,對應包含它的項也要改變{ while(i<=N) { a[i]+=plus; i+=lowbit(i);///後繼改變 }}inline int getsum(int k)///求第1到第k項的和{ int sum=0; while(k) { sum+=a[k]; k-=lowbit(k); } return sum;}int main(){ int n; while(~scanf("%d",&n)) { memset(a,0,sizeof(a)); int x; for(int i=1;i<=n;i++) { scanf("%d",&x); update(i,n,x); } for(int i=1;i<=n;i++) { printf("%d ",a[i]); } printf("求前5項和:\n"); printf("%d\n",getsum(5)); printf("求3-6項和:\n"); printf("%d\n",getsum(6)-getsum(3)+a[3]); } return 0;}View Code
以下數組下標均預設從1開始
應用一
假如給你一個數組a[ ] = {2,5,3,4,1},求b[i],b[i] 表示在a[1],a[2]...a[i-1]中(即位置i的左邊)小於等於a[i]的數的個數。對此例b[] = {0,1,1,2,0}。 那麼該如何去求得b[i]呢?
解法:假如要得到b[4]的值,對於a[4] = 4. 我們 只要得到在a[1],a[2],a[3] 中出現小於等於4的個數,即1,2,3,4的個數,此例即為2. a[1] = 2 < a[4], a[3] = 3 < a[4]. 所以b[4] = 2;其他的以此類推. 求b[i]的值,需要得到在a[1],a[2]....a[i-1]中出現小於等於a[i]的個數,即1,2...a[i]的個數. 相當於求前a[i]項的和,可用到樹狀數組.
具體操作
for(int i=1; i<=n; i++)
{
b[i] = getSum(a[i]); //求前a[i]項的和
update(a[i],1); //第a[i]個元素+1
}
應用二
假如給你一個數組a[ ] = {2,5,3,4,1},求b[i],b[i] 表示在a[1],a[2]...a[i-1]中(即位置i的左邊)大於等於a[i]的數的個數。對此例b[] = {0,0,1,1,4}。 那麼該如何去求得b[i]呢?
解法1: 只需要先將數組a倒過來編號,即將a轉換為,a[] ={4,1,3,2,5}.此時具體的操作如應用一
解法2:改變更新路徑和求和路徑
Binary Indexted Tree 樹狀數組入門