樹鏈剖分 入門(求LCA)

2021-10-23 19:28:03 字數 2606 閱讀 2527

顧名思義,樹鏈剖分就是將樹剖分成一條一條的鏈,之後快速的操作鏈。

樹鏈剖分的複雜度為,預處理兩遍dfs,o(n)複雜度;之後查詢或者修改操作每一條鏈為o(log(n)),因為剖分出來的鏈一共有log(n)條,當然如果在鏈上再套個線段樹,那每次操作就是o(log2(n))。之後,下文只利用樹鏈剖分求樹上兩點的最近公共最先,不涉及線段樹,了解了基本過程,我相信,再套個線段樹也不是難事。

目前,樹鏈剖分的有以下幾種:重鏈剖分o(log(n))、長鏈剖分o(n

\sqrt

n​)和實鏈剖分(lct)。重鏈剖分用的最多,複雜度也較好,也就是鏈的條數,下面著重敘述什麼是重鏈剖分。

「重」的定義:子樹結點個數。於是,得出了重兒子的定義:幾個兒子結點中,子樹結點數量最多的,那個兒子就是重兒子。有點抽象,看圖:

圖中紅色的點代表重兒子(當然1號根結點,我也標成了紅色),以1號結點為例,他的兒子有2、8、11,2號子樹結點有,8號子樹結點有,11號子樹結點有,可以得出2號子樹結點數量最多,因此2號是1號結點的重兒子,為紅色,8和11號是1號結點的輕兒子,為黃色。其他的結點依次類推。

重鏈的定義(個人見解):以輕兒子為起點(或者整個樹的根結點),依次向下所有重兒子組成的鏈是重鏈,如圖中紅色的線,都是重鏈。而黃色的線為輕鏈,因此有輕鏈的定義:以重鏈為主幹鏈,其分支的鏈為輕鏈,且向外擴充套件長度為1。

圖中重鏈有:、、;輕鏈有、、、、,這裡發現輕鏈長度有大於1的,如何理解擴充套件長度為1,我們首先以重鏈向外擴充套件1,有,再以向外擴充套件1,有,因此是沒問題的。

從上面的分析可以看出這些性質,輕鏈本質就是,一些長度為1的邊,將他們作為「橋梁」連線下乙個重鏈;也可以看出,輕兒子結點,都是下一條重鏈的起始結點(除去某些葉子結點,因為只有乙個結點,也說不上是鏈,圖中的3、6等);

那麼重點是,我們得到一系列的重鏈,如何對他們快速操作?答案是記錄,重鏈的頭結點,如這條鏈,我們就標記

那麼我們就能直接跳到重鏈的頭部。那麼有個問題啦,在同一條重鏈上可以直接跳到頭部,如果不是在同一條鏈上呢?答案是,從這條重鏈的頭部再往上跳,到他的父親結點,必定在另外一條重鏈上,然後根據需求繼續跳。如圖所示,b結點跳躍的過程:

b在這條重鏈,跳至6號結點,從6號再跳到這條重鏈,再跳就是1根節點。到這裡,再想想什麼輕鏈,理解會更深刻「將他們作為「橋梁」連線下乙個重鏈」。

同樣以上圖為例,求ab兩點的最近公共祖先。1、誰先跳,2、什麼時候可以判斷出,lca。

答案1,深度較大的先跳,問題來了,圖中a、b深度一樣啊?不不不,不是直接比較a、b結點的深度,比較a和b向上跳乙個鏈的深度,b結點所在的重鏈,往上,就是4號結點了,a結點所在的重鏈是最初的重鏈,往上,也只能是1號結點,我們比較1號和4號結點的深度即可,發現4號結點深度大,那麼我們優先跳b結點,至4號結點。用father陣列記錄每個結點的父結點,即**為 b=father[top[b]],其中top[b]=6。

答案2,如果兩個點在同一重鏈上,那麼深度較小的,為lca,否則還是要不斷的跳。a、b結點,當b結點跳到4結點時,a和4在同一重鏈中,即top[a]==top[4],所以4為a、b最終的lca。

經過上面的分析,需要這些陣列:

第一遍dfs得到son,depth,father,siz,第二遍dfs通過son,father得出top。第一遍dfs比較簡單,重點說說第二遍dfs,首先根據son,先dfs所有的重鏈,遍歷重鏈過程中,傳遞下去top值;再以重鏈為主幹,遍歷所有的輕鏈,因為輕結點會是重鏈的起點,因此,又可以得到新的重鏈,依次下去。

#includeusing namespace std;

#define ios ios::sync_with_stdio(false)

#define ll long long

#define maxn 500005

#define mod 99999997

#define inf 1000000007

struct edge

g[maxn*2];

int head[maxn],num;

void add_edge(int from,int to)

int siz[maxn];

int depth[maxn];

int son[maxn];

int top[maxn];

int father[maxn];

void dfs1(int fa,int cur,int dd)}}

son[cur]=hid;

}void dfs2(int cur,int tt)

}int query(int u,int v)

dfs1(s,s,1);

dfs2(s,s);

while(m--)

return 0;}/*

6 100

1 11

1 81 2

2 72 4

2 34 6

4 55 15

6 16

8 99 10

11 13

11 12

13 14

*/

樹鏈剖分求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 ...