樹鏈剖分

zla_2012發表於2024-10-25

樹鏈剖分 在 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);
    }
  }
}

相關文章