演算法詳解 樹鏈剖分

2021-10-09 05:56:46 字數 2133 閱讀 9720

1、給你乙個序列,再給你一堆詢問區間,對於每個詢問區間,請你求區間內的最大值、累加和等等。

對於這個問題,我們是早就做爛的了,線段樹、樹狀陣列等資料結構都能輕鬆求,這裡不再詳述。

2、給你一棵樹,再給你一堆詢問,每次給你兩個點,讓你求兩個點之間的路徑中的點權最大值、點權和等等。

對於這個問題,我們很顯然不能再像問題1

11一樣樣直白的去做,因為樹的路徑與純粹的區間不同。那我們能否用乙個演算法,將一棵樹剖分成若干個區間(鏈),在用這些區間用1

11一樣的方法去求的。

是的,這個演算法就叫做——

我們將一棵樹剖分成若干條鏈,而每條鏈如果我們能用區間的方式去求答案,最基本的要求就是鏈上的每個點的編號是連續的,這也是乙個區間的基本條件。

那麼如何保證鏈上的點的編號連續呢?

我們引入以下幾種概念:

1、重兒子:siz

esize

size

最多的兒子

2、輕兒子:除了重兒子之外的兒子

3、重鏈:以重兒子為首的鏈

4、輕鏈:同理。

5、t op

[x

]top[x]

top[x]

:當前點x

xx所處的重鏈的編號(即重兒子的編號)

6、n um

[x]:

num[x]:

num[x]

:遍歷到x

xx的dfs序

預處理:

對於上述問題,我們可以以下處理:

一旦遇到重兒子,就優先遍歷重兒子,直到結束。對於每個輕兒子,也是優先遍歷重兒子,直到結束……如此迴圈,就能保證每一條鏈都是連續的。

通過這樣的操作我們就把一棵樹轉化為了若干段區間。

如下圖所示:

四個顏色分別對應著四條鏈,點上的編號是dfs序。

這是預處理部分,**如下:

void

dfs1

(int x,

int dd,

int faa)

}void

dfs2

(int x,

int id)

}

答案求解:

給你兩個點x

xx,y

yy,讓你求x,y

x,yx,

y路徑之間的最大值、權值和。

類似於倍增求lca

lcalc

a的思想,如果兩個點不在同一條鏈上,我們就將深度大的點不斷往上跳,直到將兩個點跳到同一條鏈上為止。

假設深度大的點編號為x

xx,那麼往上跳一次,對答案的代價是:

a sk

(num

[top

[x]]

,num

[x])

ask(num[top[x]],num[x])

ask(nu

m[to

p[x]

],nu

m[x]

)即當前鏈的答案。

ask函式的具體內容據題目而定,一般用線段樹或者樹狀陣列來維護。

當x

xx和y

yy跳到同一深度後,貢獻就是:

a sk

(x,y

)ask(x,y)

ask(x,

y)x

xx和y

yy深度的先後順序據題目而定,切記不可搞錯。

模板如下:

int

ask(

int x,

int y)

if(d[x]

< d[y]

)swap

(x,y)

; ans+

=tr.

ask(num[x]

)- tr.

ask(num[y]-1

);return ans;

}

以上兩部分就是樹鏈剖分的核心內容。

總之樹鏈剖分就是將樹上路徑問題轉化為序列問題的過程,對於求解各種樹上路徑問題有莫大的幫助。

大致複雜度:o(n

log2

n)

o(nlog^2n)

o(nlog

2n)

詳見

樹鏈剖分演算法詳解

學oi也有一段時間了,感覺該搞點東西了。於是學習了樹 熟 鏈 練 剖 pou 分 糞 當然,學習這個演算法是需要先學習線段樹的。不懂的還是再過一段時間吧。如果碰到一道題,要對一顆樹的兩個點中的最短路徑 以u為根的子樹之類的東西進行修改或者查詢,那麼大概就是樹鏈剖分的題了。為什麼是盡可能?因為在一棵樹...

樹鏈剖分詳解

樹鏈剖分定義 只是把一棵樹拆成鏈來處理而已,即將樹上的某些段一起通過資料結構優化進行處理來降低複雜度。樹鏈剖分相關定義 重兒子 ve v 為 u 的子節點中ve 值最大的,那麼 v 就是 u的重兒子 將子樹中最長的那一條鏈一起處理來降低複雜度 輕兒子 u 除了重兒子的其它子節點。重邊 點 u與其重兒...

詳解樹鏈剖分

樹鏈剖分,顧名思義為將鏈剖開分成多條。當我們想要修改樹上一條路的值或求值時,我們暴力只能用乙個個修改,這是非常慢的。這時,我們就要想乙個辦法,資料結構?但是資料結構我們都需要連續修改,可是樹上路徑的編號是不連續的。於是我們想了乙個辦法。我們先定義 fa x 為x的父親 dep x 為x的深度 siz...