樹鏈剖分 在 DFS
樹上把連續的一段有祖先關係的單獨開一個序列儲存。
查詢每一個位置, 不斷地往鏈頭條, 然後跳到鏈頭的父親的鏈上 \(\dots\)
如果按 DFS
徐直接搞, 會被以下資料 hack
可行的序列有 \(:[1 10], [2, 10], [3, 12], [4, 13], [5, 14], [6, 15], [7, 16], [8, 17], [9, 18]\), 如果查詢從 \(18 \to 1\), 單次時間複雜度 \(O(N)\)
重鏈最佳化
對於一個節點, 選他兒子子樹最大的兒子最當前節點鏈的後續。
這樣, 考慮一個節點往根跳, 每一次, 為了不會父親在一個重鏈上, 至少有一個兄弟的節點數量 \(\ge\) 當前的數量, 所以為了讓這個節點到根經過 \(k\) 個重鏈, 整個樹至少要有 \(2^k\) 個節點。所以如果有 \(n\) 個點, 單次查詢最壞是 \(O(\log_2 n)\)。
這樣子的序列也滿足 DFS
序的性質
虛擬碼
void dfs1(int u){
sz[u] = 1;
for(auto v : g[u]){
dfs1(v);
sz[u] += sz[v];
if(sz[son[u]] < sz[v]){
son[u] = v;
}
}
}
void dfs2(int x){
dfn[x] = ++cnt;
if(son[x]){
top[son[x]] = top[x];
dfs2(son[x]);
}
for(auto v : g[x]){
if(v != son[x]){
top[v] = v;
dfs2(v);
}
}
}