樹鏈剖分與倍增求LCA

2022-09-13 04:09:14 字數 3550 閱讀 3665

首先我要吐槽機房的辣基供電情況,我之前寫了一上午,馬上就要完成的時候突然停電,然後\(gg\)成了送鏈剖分

其次,我沒歧視\(tarjan lca\)

理解較為簡單的一種方法,但速度略慢

每個數字都可以拆成幾個二的整數次的和,我們可以找出每個數字是由哪幾個二的整數次的數合成的

比如說\(14 _ = 1110_2 = 1000 _2 + 100 _2 + 10 _2 = 8 _ + 4 _ + 2 _\)

那麼我們如果要統計一段長度為十四的區間的最小值,我們就可以先統計前八個數的最小值,再統計之後的四個,再統計之後的兩個。

我們可以用\(f[i][j]\)表示從\(i\)開始往後\(2^j\)長度的最小值

給寧康康**

for( int i = 1; i <= 23; i++ )

}

下面這個東西是啥意思呢

f[i][j - 1], f[a[i][j - 1]][j - 1]
\(2^j = 2^ +2^\) 比如說 \(2^4 = 2 ^ 3 +2 ^ 3\)

在每個葉節點到根節點的鏈上做倍增

演示一哈(如果開啟我的部落格就會發現我這種蒟蒻說不清話只會畫圖

應該比較顯然吧???

我們在求\(lca\)前\(dfs\)一遍,統計出每個葉節點的\(f[i][1]\)(也就是父節點)和\(dep[i]\)(就是該節點所處深度,規定根節點深度為1)。然後跑一遍倍增,預處理每個葉節點的向上\(2^i\)個祖宗是誰。

然後倍增求\(lca\),我們可以先看兩個點是否在同一深度,不在的話就把比較低的那個點往上走一走,直到走到同一深度。注意在跳的時候要從大到小列舉,給寧康康**,寧再把上面的\(14\)那個例子帶進去從\(1\)到\(20\)列舉一下子就懂了

inline int lca( int x, int y )

if( x == y ) return x;

for( rint i = 20; i >= 0; i-- )

return f[x][0];

}

#includeusing namespace std;

#define rint register int

int n, m, s, cnt, dep[500005], f[500005][23], head[500005];

struct edgea[500005<<1];

inline int read( void )

while( ch >= '0' && ch <= '9' )

return re * f;

}inline void addedge( int x, int y )

inline void dfs( int x, int fa )

for( rint i = head[x]; i; i = a[i].nxt )

return ;

}inline int lca( int x, int y )

if( x == y ) return x;

for( rint i = 20; i >= 0; i-- )

return f[x][0];

}int main( void )

dfs( s, 0 );

int u, v;

for( rint i = 1; i <= m; i++ )

return 0;

}

樹鏈剖分其實有好多種剖分方法,但這裡只介紹輕重邊剖分

乙個節點只能有乙個重兒子。

鏈 : 連續的重/輕邊構成一條鏈。(圖中從\(1\)到\(14\)即一條重鏈)

\(dep[i]\) : \(i\)節點的深度,規定根節點深度為\(1\)。

\(fa[i]\) : \(i\)節點的父親。

\(son[i]\) : \(i\)節點的重兒子。

\(siz[i]\) : 以\(i\)節點為根的子樹的大小。

\(top[i]\) : \(i\)所在鏈的根。(圖中從\(1\)到\(14\)的鏈的根為\(1\))

重邊 : 以\(i\)節點的兒子中\(siz\)最大的兒子到\(i\)的連邊,即圖中的粗邊(該兒子也叫重兒子)。

輕邊 : 處重邊外的其他邊。

首先\(dfs\)一遍,求出\(dep\),\(fa\),\(son\),\(siz\),容易實現,給寧康**

inline void dfs1( int now, int father, int de )

}}

然後再\(dfs\)一遍,處理出每個點的\(top\),也就是所在鏈的頂點,其中輕鏈的頂點是它自己。

給寧康康**

inline void dfs2( int now, int topf )

}

如果乙個點要跳到它的\(lca\),就一定會跳到它的\(lca\)所在鏈(廢話……

那麼我們要判定是否已經找到\(lca\),就只需要看當前兩點\(xy\)是否在同一條鏈上,其實就是看兩個點的\(top\)是否相等,如果不相等的話,我們就讓深度大的點一次性跳完一整條重鏈,然後再跳一步,走上另一條重鏈,再重複以上比較\(top\),跳重鏈的過程

我們可以發現非葉節點一定在某條重鏈上,所以我們一次性跳完一條重鏈,再跳一步,就會跳上另一條重鏈,所以查詢\(lca\)的複雜度是小於\(logn\)的

給寧康康**

inline int lca( int x, int y )

if( dep[x] > dep[y] ) return y;

return x;

}

全部**:

#includeusing namespace std;

#define rint register int

int t, n, m;

int son[1000010], fa[1000010], siz[1000010], dep[1000010], top[1000010];

vector< int > vec[1000010];

inline int read( void )

while( ch >= '0' && ch <= '9' )

return re * f;

}inline void dfs1( int now, int father, int de ) }}

inline void dfs2( int now, int topf )

}inline int lca( int x, int y )

if( dep[x] > dep[y] ) return y;

return x;

}int main( void )

dfs1( 1, 1, 1 );

dfs2( 1, 1 );

for( rint i = 1; i <= m; i++ )

return 0;

}

如果要求樹上兩點最短距離,可以求\(lca\)

\(dis = dep[x] + dep[y] - 2 * lca\)

樹鏈剖分求LCA

這裡先推薦兩道練習的裸題 首先是求點 codevs4605 lca 就是求兩個點的公共祖先,每次詢問xor上上乙個詢問的答案。先是兩遍dfs dfs1 把dep siz son求出來 dfs2 求出top和w siz v 表示以v為根的子樹的節點數 dep v 表示v的深度 根深度為1 top v ...

樹鏈剖分求lca

題目描述 給一棵有根樹,以及一些詢問,每次詢問樹上的2 個節點a b,求它們的最近公共祖先.輸入第一行乙個整數n.接下來n 個數,第i 個數fi 表示i 的父親是fi.若fi 0,則i 為樹根.接下來乙個整數m.接下來m 行,每行2 個整數a b,詢問節點 a xor lastans bxor la...

樹鏈剖分(1) 樹剖求LCA

先看乙個樹剖的經典應用 初始化 先dfs一遍子樹,統計出每乙個點x的重兒子son x 和以x為根節點的子樹的大小siz x 這裡選擇x中子樹大小最大的兒子作為它的重兒子,第二遍dfs劃分樹鏈 重兒子與其父親節點劃分到一條鏈。其他的兒子為x的輕兒子,但屬於新的鏈的頂端元素。void dfs1 int ...