關於樹狀陣列一些有意思的東西

2021-07-11 11:56:08 字數 2743 閱讀 3718

嘛~最近剛剛學會樹狀陣列,寫個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.想象切換 英雄 中,無名和秦始皇各自的想象演繹。各種影視...