關於樹狀陣列一些有意思的東西
嘛~最近剛剛學會樹狀陣列,寫個blog記錄一下心得。
樹狀陣列呢,核心是一個叫lowbit的東西,lowbit(x)=x&-x=x的最後一位1的大小。
一、一個經典問題
一個初始值為0的k位計數器,要求支援n次+1操作。時間複雜度?
經典解法:
法I:考慮第i位的改變次數,可得
O(∑k−1i=0n2i)≤O(∑∞i=0n2i)=O(n) O(\sum_{i=0}^{k-1}{n\over 2^i})\le O(\sum_{i=0}^{\infty}{n\over 2^i})=O(n)。
法II:考慮計數器中1的數量,顯然每次只會增加1個(減少若干個),所以時間複雜度O(n) O(n)。
法III:考慮勢能函式f(T)=計數器中1的數量,則顯然單次操作均攤O(1) O(1)。
奇怪向做法:
對於x,考慮大於x的第一個y使得y&lowbit(x)==0(lowbit(x)第一次被進位),顯然y=x+lowbit(x)。所以由x向x+lowbit(x)連一條邊。這樣的話顯然會形成一棵樹,計數器操作的代價就是點數+邊數等於
O(n) O(n)。
這棵樹就是樹狀陣列啦!
二、樹狀陣列中的一些基本關係
x的父親是x+lowbit(x)。
x的子樹是(x-lowbit(x),x],(即所有能通過若干次+lowbit到達x的節點集合)。
考慮x一直沿著它父親走,那麼lowbit一定是嚴格單增的,所以樹高是
考慮x的兒子,就是能通過一次+lowbit操作到達x的元素數量,它顯然等於
三、基本的樹狀陣列怎麼寫?
我們先來考慮一個簡單的問題,就是求區間和。要求支援單點修改,區間詢問。
那麼我們對於每個節點儲存它的子樹的和。
修改每個節點的時候直接沿著父親一路找上去就可以了。
void add(int x,int delta){
for(;x<=n;x+=x&-x)bit[x]+=delta;
}
查詢的時候我們可以把區間和改為兩個字首和的差。而一個字首和可以拆分成
int query(int x){
int ans=0;
for(;x;x-=x&-x)ans+=bit[x];
return ans;
}
四、如何初始化樹狀陣列?
比如說我們有一個陣列a,我們要建出它的bit,我們該怎麼做呢?
我以前的做法是把n個數插入進去。
但顯然這是不必要的。
我們可以從1~n遞推,假設推到i時bit[i]已經推出來了,那麼顯然它只需貢獻給bit[i+lowbit(i)]即可。
void build(){
for(int i=1,x;i<=n;++i){
bit[i]+=a[i];
if((x=i+(i&-i))<=n)bit[x]+=bit[i];
}
}
五、維護最值?
顯然,樹狀陣列維護最值的話,只能支援兩種操作:增大一個位置的數,查詢字首最值。
這看起來非常苛刻,但是其實在很多情況下,都是滿足的。最常見的是bit+掃描線/dp這種的。
但是,如果時間複雜度允許是
初始化當然不必說。
void build(){
for(int i=1,x;i<=n;++i){
bit[i]=max(bit[i],a[i]);
if((x=i+(i&-i))<=n)bit[x]=max(bit[x],bit[i]);
}
}
修改的時候,我們只需要修改x的
void update(int x,int A){
a[x]=A;
for(int y,i;x<=n;x+=x&-x){
bit[x]=a[x];
for(y=x-1,i=1;y&i;y-=i,i<<=1)bit[x]=max(bit[x],bit[y]);
}
}
查詢[l,r]的時候我們把它分成[l,r]路徑上的點和被完全覆蓋的子樹兩部分,因為[l,r]路徑上只有
int query(int l,int r){
int ans=-0x7fffffff;
--l;
for(int x;r>l;--r){
for(;r&&r-(r&-r)>=l;r-=r&-r)ans=max(ans,bit[r]);
if(r>l)ans=max(ans,a[r]);
}
return ans;
}
當然。。這
六、維護字尾?
Q:如何支援單點修改,字尾和查詢?
A:= =這不跟區間查詢一樣麼。
Q:查兩邊字首和?常數太大!不開心。
A:那就把原陣列反過來不就行了麼。
Q:座標什麼的反來反去,很麻煩的。好煩人,不開心!
A:。。。
其實。。我們只需要把修改和詢問改一下下就好了!
先上程式碼:
void build(){
for(int i=n;i;--i){
bit[i]+=a[i];
bit[i-(i&-i)]+=bit[i];
}
}
void add(int x,int delta){
for(;x;x-=x&-x)bit[x]+=delta;
}
int query(int x){
int ans=0;
for(;x<=n;x+=x&-x)ans+=bit[x];
return ans;
}
←_←看起來就像是寫殘了的樹狀陣列。。
但為什麼可以這樣搞?!
我們可以將修改看成是在樹上打永久化的標記,查詢就是在收集標記。
但是,我們需要更高逼格的解釋方法。
注意到樹狀陣列中x的父親是x+lowbit(x),而如果x+lowbit(x)>n,那麼其實它的父親是不存在的,就是說其實它是一棵森林。我們在build的時候為了防止陣列越界,還要特判一下,好煩人!
所以我們不妨把x的父親改為x-lowbit(x),這樣就是一棵以0為根的樹啦!這樣的話,x的子樹就是[x,min(n,x+lowbit(x)-1)]。上述程式碼就變得顯而易見了。
相關文章
- 樹狀陣列陣列
- 關於引用型別轉化的一些東西型別
- 解析樹狀陣列陣列
- 關於 智慧指標的東西指標
- 樹狀陣列詳解陣列
- 樹狀陣列基礎陣列
- poj 2481 樹狀陣列陣列
- hdu 3874 樹狀陣列陣列
- 二維樹狀陣列陣列
- 2024年暑假關於線段樹和樹狀陣列的小知識點陣列
- 樹狀陣列模板題 & (樹狀陣列 1:單點修改,區間查詢)陣列
- JavaScript關於陣列的一些方法整理JavaScript陣列
- 樹狀陣列和逆序對陣列
- hdu 5147 樹狀陣列陣列
- 【筆記/模板】樹狀陣列筆記陣列
- 樹狀陣列快速入門陣列
- 近期做的一些東西
- CF 293 E Close Vertices (樹的分治+樹狀陣列)陣列
- 樹狀陣列模板+習題集陣列
- 樹狀陣列3種基本操作陣列
- 學習筆記----樹狀陣列筆記陣列
- 樹狀陣列upc1976陣列
- CSU 4441 Necklace (樹狀陣列/LIS)陣列
- 樹狀陣列(我是真小白)陣列
- 資料結構——樹狀陣列資料結構陣列
- 關於區間操作查詢(字首和與差分)+樹狀陣列基礎陣列
- TCP Socket一些東西TCP
- 線段樹+差分——【模板】樹狀陣列2陣列
- hdu 4836 The Query on the Tree(線段樹or樹狀陣列)陣列
- thymeleaf關於js的一些坑(陣列定義)JS陣列
- html入門的一些東西HTML
- 10:Challenge 3(樹狀陣列直接修改)陣列
- POJ 3928 Ping pong(樹狀陣列)陣列
- 關於 Gradle 依賴庫的幾個東西Gradle
- Cesium筆記----關於viewer的配置及常用東西筆記View
- HDU 1556 Color the ball(線段樹|樹狀陣列)陣列
- HDU 1166 敵兵佈陣 (樹狀陣列)陣列
- HDU 1166 敵兵佈陣(樹狀陣列)陣列