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...