最近公共祖先 LCA 倍增演算法

2022-07-08 19:48:10 字數 4063 閱讀 5707

樹上倍增求lca

lca指的是最近公共祖先(least common ancestors),如下圖所示:

4和5的lca就是2

那怎麼求呢?最粗暴的方法就是先dfs一次,處理出每個點的深度

然後把深度更深的那乙個點(4)乙個點地乙個點地往上跳,直到到某個點(3)和另外那個點(5)的深度一樣

然後兩個點一起乙個點地乙個點地往上跳,直到到某個點(就是最近公共祖先)兩個點「變」成了乙個點

不過有沒有發現乙個點地乙個點地跳很浪費時間?

如果一下子跳到目標點記憶體又可能不支援,相對來說倍增的價效比算是很高的

倍增的話就是一次跳2i 個點,不難發現深度差為x時,深度更深的那個點就需要跳x個點,

於是可以寫出這段**

if(depth[a] < depth[b])    swap(a, b);

int c = depth[a] - depth[b];

for(int i = 0; i <= 14; i++)

}

接下來很快就會發現乙個很嚴重的問題:兩個點按照這樣跳,不能保證一定是最近的。所以倍增找lca的方法是這樣的:從最大可以跳的步數開始跳(一定是2i),如果跳的到的位置一樣,就不跳,如果不一樣才跳,每次跳的路程是前一次的一半

過程大概就像上圖所示,但是執行完了這一段到的點不是最近公共祖先,但是,它們再往上跳一格,就到了

把這一段寫成**,就成了這樣:

for(int i = 14; i >= 0; i--)

}

前面還需要加上一句特判(當a和b在同一邊時,深度淺的那個點就是最近公共祖先) if(a == b)  return a;

好了,會求lca了,關鍵是怎麼構造倍增陣列。沒有疑問的是向上跳一格就是自己的父節點

f[i][0] = fa[i];

這個是初值,接著可以根據這個推出來其他的,除此之外還要附上初值0,不然有可能會re

f[i][j] = f[f[i][j - 1]][j - 1];

就是把這一段路,分成兩段已經知道的

完整**就是這樣的:

matrixup;

inline void init_bz()

for(int j = 1; j <= 14; j++)}}

注意倍增求lca適用於詢問多的情況,不然光在預處理上花的時間就已經夠多了。

1. dfs預處理出所有節點的深度和父節點

版本1

void dfs(int u)

}

版本2

inline void dfs(int

u) }

}

2. 初始各個點的2^j祖先是誰 ,其中 2^j (j =0...log(該點深度))倍祖先,1倍祖先就是父親,2倍祖先是父親的父親......。i的2^j祖先就是i的(2^(j-1))祖先的2^(j-1)祖先:

void init()
3.從深度大的節點上公升至深度小的節點同層,如果此時兩節點相同直接返回此節點,即lca。否則,利用倍增法找到最小深度的 p[a][j]!=p[b][j],此時他們的父親p[a][0]即lca。

版本1:

int lca(int a,int b)

}a=p[a][0]; //這時a的father就是lca

}return a;

}

版本2

int lca(int a,int b)//最近公共祖先

} return p[a][0];

}

lca指的是最近公共祖先(least common ancestors),如下圖所示:

4和5的lca就是2

那怎麼求呢?最粗暴的方法就是先dfs一次,處理出每個點的深度

然後把深度更深的那乙個點(4)乙個點地乙個點地往上跳,直到到某個點(3)和另外那個點(5)的深度一樣

然後兩個點一起乙個點地乙個點地往上跳,直到到某個點(就是最近公共祖先)兩個點「變」成了乙個點

不過有沒有發現乙個點地乙個點地跳很浪費時間?

如果一下子跳到目標點記憶體又可能不支援,相對來說倍增的價效比算是很高的

倍增的話就是一次跳2i 個點,不難發現深度差為x時,深度更深的那個點就需要跳x個點,

於是可以寫出這段**

if(depth[a] < depth[b])    swap(a, b);

int c = depth[a] - depth[b];

for(int i = 0; i <= 14; i++)

}

接下來很快就會發現乙個很嚴重的問題:兩個點按照這樣跳,不能保證一定是最近的。所以倍增找lca的方法是這樣的:從最大可以跳的步數開始跳(一定是2i),如果跳的到的位置一樣,就不跳,如果不一樣才跳,每次跳的路程是前一次的一半

過程大概就像上圖所示,但是執行完了這一段到的點不是最近公共祖先,但是,它們再往上跳一格,就到了

把這一段寫成**,就成了這樣:

for(int i = 14; i >= 0; i--)

}

前面還需要加上一句特判(當a和b在同一邊時,深度淺的那個點就是最近公共祖先) if(a == b)  return a;

好了,會求lca了,關鍵是怎麼構造倍增陣列。沒有疑問的是向上跳一格就是自己的父節點

f[i][0] = fa[i];

這個是初值,接著可以根據這個推出來其他的,除此之外還要附上初值0,不然有可能會re

f[i][j] = f[f[i][j - 1]][j - 1];

就是把這一段路,分成兩段已經知道的

完整**就是這樣的:

matrixup;

inline void init_bz()

for(int j = 1; j <= 14; j++)}}

注意倍增求lca適用於詢問多的情況,不然光在預處理上花的時間就已經夠多了。

1. dfs預處理出所有節點的深度和父節點

版本1

void dfs(int u)

}

版本2

inline void dfs(int

u) }

}

2. 初始各個點的2^j祖先是誰 ,其中 2^j (j =0...log(該點深度))倍祖先,1倍祖先就是父親,2倍祖先是父親的父親......。i的2^j祖先就是i的(2^(j-1))祖先的2^(j-1)祖先:

void init()
3.從深度大的節點上公升至深度小的節點同層,如果此時兩節點相同直接返回此節點,即lca。否則,利用倍增法找到最小深度的 p[a][j]!=p[b][j],此時他們的父親p[a][0]即lca。

版本1:

int lca(int a,int b)

}a=p[a][0]; //這時a的father就是lca

}return a;

}

版本2

int lca(int a,int b)//最近公共祖先

} return p[a][0];

}

LCA 最近公共祖先 (倍增演算法)

首先了解一下我們 最近公共祖先 e和g的lca為a l和j的lca為d k和f的lca為b 然後 倍增 用到了二進位制和 dp 的思想 倍增 就是 1 2 4 8 16 任何乙個數 都是可以右 這些數相加得到的。了解一下二進位制 首先 定義 fa i j 為 從 i 節點 向上走 2 j 個節點,d...

LCA 最近公共祖先)之倍增演算法

概述 對於有根樹t的兩個結點u v,最近公共祖先lca t,u,v 表示乙個結點x,滿足x是u v的祖先且x的深度盡可能大。如圖,3和5的最近公共祖先是1,5和2的最近公共祖先是4 在本篇中我們先介紹一下倍增演算法 我們需要乙個陣列de i 來表示每乙個節點i的深度,用另一陣列parent i j ...

樹,LCA,最近公共祖先,倍增

最近公共祖先,樹上倍增,lca,fa i j 表示 i 節點向上 2j 的祖先 很像dp,k 屬於 1 log n f x k f f x k 1 k 1 算lca時,先不妨設 d x d y 二進位制拆分 嘗試從x 向上走 k 2log n 21,20,檢查到的點是否比 y 深 每次檢查中,若是,...