嘛~最近剛剛學會樹狀陣列,寫個blog記錄一下心得。
樹狀陣列呢,核心是乙個叫lowbit的東西,lowbit(x)=x&-x=x的最後一位1的大小。
一、乙個經典問題
乙個初始值為0的k位計數器,要求支援n次+1操作。時間複雜度?經典解法:
法i:考慮第i位的改變次數,可得o(奇怪向做法:∑k−1
i=0n
2i)≤
o(∑∞
i=0n
2i)=
o(n)
。 法ii:考慮計數器中1的數量,顯然每次只會增加1個(減少若干個),所以時間複雜度o(
n)。
法iii:考慮勢能函式f(t)=計數器中1的數量,則顯然單次操作均攤o(1)。
對於x,考慮大於x的第乙個y使得y&lowbit(x)==0(lowbit(x)第一次被進製),顯然y=x+lowbit(x)。所以由x向x+lowbit(x)連一條邊。這樣的話顯然會形成一棵樹,計數器操作的代價就是點數+邊數等於o(n)。這棵樹就是樹狀陣列啦!
二、樹狀陣列中的一些基本關係
x的父親是x+lowbit(x)。
x的子樹是(x-lowbit(x),x],(即所有能通過若干次+lowbit到達x的節點集合)。
考慮x一直沿著它父親走,那麼lowbit一定是嚴格單增的,所以樹高是o(
log2n)
的。 考慮x的兒子,就是能通過一次+lowbit操作到達x的元素數量,它顯然等於
log2lo
wbit
(x) ,就等於
,所以乙個節點的兒子數量也是o(
log2n)
的。更直觀的說法是,x的兒子數量其實就等於從x-1 +1(上文中的例題)時被進製的(消失的)1的數量。
三、基本的樹狀陣列怎麼寫?
我們先來考慮乙個簡單的問題,就是求區間和。要求支援單點修改,區間詢問。
那麼我們對於每個節點儲存它的子樹的和。
修改每個節點的時候直接沿著父親一路找上去就可以了。
void add(int x,int delta)
查詢的時候我們可以把區間和改為兩個字首和的差。而乙個字首和可以拆分成o(
log2n)
棵子樹。
int query(int x)
四、如何初始化樹狀陣列?
比如說我們有乙個陣列a,我們要建出它的bit,我們該怎麼做呢?
我以前的做法是把n個數插入進去。
但顯然這是不必要的。
我們可以從1~n遞推,假設推到i時bit[i]已經推出來了,那麼顯然它只需貢獻給bit[i+lowbit(i)]即可。
void build()
}
五、維護最值?
顯然,樹狀陣列維護最值的話,只能支援兩種操作:增大乙個位置的數,查詢字首最值。
這看起來非常苛刻,但是其實在很多情況下,都是滿足的。最常見的是bit+掃瞄線/dp這種的。
但是,如果時間複雜度允許是o(
log22n
) ,樹狀陣列也是可以做到維護最值的。
初始化當然不必說。
void build()
}
修改的時候,我們只需要修改x的o(
log2n)
個祖先,而每個祖先又有o(
log2n)
個兒子。
void update(int x,int a)
}
查詢[l,r]的時候我們把它分成[l,r]路徑上的點和被完全覆蓋的子樹兩部分,因為[l,r]路徑上只有o(
log2n)
個點,所以被完全覆蓋的子樹顯然只有o(
log22n
) 個。
int query(int l,int r)
return ans;
}
當然。。這o(
log22n
) 的玩意兒顯然是沒什麼卵用的東西。僅供娛樂~
六、維護字尾?
q:如何支援單點修改,字尾和查詢?
a:= =這不跟區間查詢一樣麼。
q:查兩邊字首和?常數太大!不開心。
a:那就把原陣列反過來不就行了麼。
q:座標什麼的反來反去,很麻煩的。好煩人,不開心!
a:。。。
其實。。我們只需要把修改和詢問改一下下就好了!
先上**:
void build()
}void add(int x,int delta)
int query(int x)
←_←看起來就像是寫殘了的樹狀陣列。。
但為什麼可以這樣搞?!
我們可以將修改看成是在樹上打永久化的標記,查詢就是在收集標記。
但是,我們需要更高逼格的解釋方法。
注意到樹狀陣列中x的父親是x+lowbit(x),而如果x+lowbit(x)>n,那麼其實它的父親是不存在的,就是說其實它是一棵森林。我們在build的時候為了防止陣列越界,還要特判一下,好煩人!
所以我們不妨把x的父親改為x-lowbit(x),這樣就是一棵以0為根的樹啦!這樣的話,x的子樹就是[x,min(n,x+lowbit(x)-1)]。上述**就變得顯而易見了。
一些有意思的東西
近日,在敲 的時候,筆者發現了一些有意思的東西。在我們用迴圈的時候,常常因為一些條件,要提前結束迴圈,而在c語言中,可以打破迴圈的就是break和continue了 1.看這個 它只輸出了兩個6,由此我們可以看出break是打破整個迴圈 2.再看關於continue 可以看出,它輸出了9個6,因此c...
關於fork一些有意思的問題
最近學了fork,討論了幾個有意思的問題,對fork理解更深了一些,記錄一下 include include include int a 10 int main printf d d a,b 當我們用return 他會告訴我們段錯區。如果是exit就會正常輸出,為什麼呢?我們知道vfork之後,子程...
一些有意思的話題
1.時空切換。各種穿越劇了,大話西遊 尋秦記 2.夢境切換 莊周曉夢迷蝴蝶,盜夢空間 3.靈魂切換 各種鬼神故事中靈魂互換,靈魂附體的故事。4.意念對白 俞伯牙和鍾子期,英雄 中無名和長空的意念搏鬥,一念桃花源 中蘇東坡和陶淵明的隔空對話。5.想象切換 英雄 中,無名和秦始皇各自的想象演繹。各種影視...