004.最近公共祖先
最近公共祖先(LCA)
倍增算法
建立两个数组:
\(dep[u]\)存储\(u\)点的深度;
\(fa[u][i]\)存储\(u\)点向上\(2^i\)层的祖先节点\((i = 0,1,2···)\).
1.DFS树,创建ST表
倍增递推:\(fa[u][i] = fa[fa[u][i-1][i-1]]\)
2.利用ST表求LCA
\((1)\)若\(x,y\)不在同一层,则让\(x,y\)先跳到同一层
\((2)\)到达同一层后,将\(x,y\)一起跳到LCA的下一层
时间复杂度:\(O((n+m)\log\ n)\)
[P3379 【模板】最近公共祖先(LCA)](P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
#include<iostream>
#include<vector>
using namespace std;
const int N = 5e5 + 5;
vector<int> e[N];
int n, m, a, b, s;
int dep[N], fa[N][20];
inline void add(int u, int v) {
e[u].push_back(v);
e[v].push_back(u);
}
void dfs(int u, int father) {
dep[u] = dep[father] + 1;
fa[u][0] = father;
for (int i = 1; i <= 19; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for (int v : e[u]) {
if (v != father) dfs(v, u);
}
}
int lca(int u, int v) {
if (dep[u] < dep[v]) swap(u, v);
//先跳到同一层
for (int i = 19; i >= 0; i--)
if (dep[fa[u][i]] >= dep[v])
u = fa[u][i];
if (u == v) return v;
//一起跳到LCA的下一层
for (int i = 19; i >= 0; i--)
if (fa[u][i] != fa[v][i])
u = fa[u][i], v = fa[v][i];
return fa[u][0];
}
int main() {
cin >> n >> m >> s;
while (n > 1) {
n--;
int x, y;
cin >> x >> y;
add(x, y);
}
dfs(s, 0);
while (m--) {
cin >> a >> b;
cout << lca(a,b) << endl;
}
return 0;
}
Tarjan算法
Tarjan是一种离线算法,利用并查集维护祖先节点
\(e[u]\)存树边;
\(query[u]\)存查询;
\(fa[u]\)存父节点;
\(vis[u]\)打标记;
\(ans[i]\)存查询结果.
查询过程:
\((1)\)从根开始DFS,进入节点\(u\)后对其打上标记\((vis[u]=true)\);
\((2)\)枚举节点\(u\)的儿子节点\(v\),遍历\(v\)的子树,返回\(u\)时,把\(v\)指向\(u(fa[v]=u)\);
\((3)\)遍历完\(u\)的儿子们,离开\(u\)返回上一层前,枚举以\(u\)为起点的查询,若终点\(v\)被搜索过\(vis[v]=true\),则查找\(v\)的根\(find[v]\),即\(u,v\)的LCA,答案计入\(ans[]\);
\((4)\)递归遍历完整棵树,得到全部查询答案.
时间复杂度:\(O(n+m)\)
[P3379 【模板】最近公共祖先(LCA)](P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))
#include<iostream>
#include<vector>
using namespace std;
const int N = 5e5 + 5;
vector<int> e[N];
int n, m, a, b, s;
int fa[N],vis[N],ans[N];
vector< pair<int,int> > query[N];//query[u]表示以u为起点的查询,.first表示终点,.second表示编号
inline void add(int u, int v) {
e[u].push_back(v);
e[v].push_back(u);
}
int find(int u){//带状态压缩的并查集查找
if(u == fa[u]) return u;
return fa[u] = find(fa[u]);
}
void tarjan(int u){
vis[u] = true;//关键点1 进入u节点:打上标记
for(auto v : e[u]){
if(!vis[v]){
tarjan(v);//递归遍历,进入儿子节点
fa[v] = u;//关键点2 从每个儿子节点回到u时:v的父节点标记为u
}
}
//关键点3 离开u进入其父节点时 枚举以u为起点的查询,找到LCA
for(auto q : query[u]){
int v = q.first,i = q.second;
if(vis[v]) ans[i] = find(v);//时间戳中包含v才能进行查找,这也是为什么双向建立查询
}
}
int main(){
for(int i = 1 ; i <= N ; i ++) fa[i] = i;
cin >> n >> m >> s;
for(int i = 1 ; i < n ; i ++){
cin >> a >> b;
add(a,b);
}
for(int i = 1 ; i <= m ; i ++){
cin >> a >> b;
query[a].push_back({b,i});
query[b].push_back({a,i});
}
tarjan(s);
for(int i = 1 ; i <= m ; i ++){
cout << ans[i] << endl;
}
return 0;
}

浙公网安备 33010602011771号