【C++】图论|最近公共祖先(LCA)模板
最近公共祖先(LCA)是图论中一类常见的问题。这里我们主要考虑的是两个节点的最近公共祖先。
题目:P3379 【模板】最近公共祖先(LCA)(https://www.luogu.com.cn/problem/P3379)
朴素法(直接dfs)
直接一层一层搜索祖先,即普通dfs。显然无法通过此题目,这里就不提供代码了。
倍增法
是朴素的改进,理解起来很简单。就是先把两个点提到树的同一高度,再一起往上跳,直到相遇。
但是代码实际上不是很好理解,研究了足足一天(也许笔者比较笨吧)
属于在线算法(即每次查询都现算),时间复杂度O(nlog n)
*这里为了降低常数,预处理了log2
// 倍增算法
#include <iostream>
#include <algorithm>
#include <vector>
#include <utility>
using namespace std;
vector<vector<int>> fa, edg;
vector<int> depth, lg2;
void dfs(int now, int fath)
{
fa[now][0] = fath;
depth[now] = depth[fath] + 1;
for (int i = 1; i <= lg2[depth[now]]; i++)
{
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
for (auto& i : edg[now])
{
if (i != fath) dfs(i, now);
}
}
int lca(int x, int y)
{
if (depth[x] < depth[y]) swap(x, y);
while (depth[x] > depth[y])
{
x = fa[x][lg2[depth[x] - depth[y]] - 1];
}
if (x == y) return x;
for (int k = lg2[depth[x]] - 1; k >= 0; k--)
{
if (fa[x][k] != fa[y][k])
{
x = fa[x][k];
y = fa[y][k];
}
}
return fa[x][0];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m, s, x, y;
cin >> n >> m >> s;
fa.resize(n + 1);
edg.resize(n + 1);
depth.resize(n + 1);
lg2.resize(n + 1);
// 预处理log2
for (int i = 1; i <= n; i++)
{
lg2[i] = lg2[i - 1] + (1 << lg2[i - 1] == i);
}
for (auto& i : fa) i.resize(lg2[n] + 1);
for (int i = 1; i < n; i++)
{
cin >> x >> y;
edg[x].emplace_back(y);
edg[y].emplace_back(x);
}
dfs(s, 0);
while (m--)
{
cin >> x >> y;
cout << lca(x, y) << endl;
}
return 0;
}

LCA Tarjan
前置知识:并查集
注意:不要和连通性的SCC Tarjan搞混,我的一位同学就做出过只因看见算法名字一样便用LCA Trajan处理缩点的逆天操作,还抱怨咋改也不过😅
LCA Tarjan是一种离线算法,即预处理所有查询。
一点小知识:并查集,LCA Tarjan,以及强连通下的缩点SCC Tarjan,双连通下割点/桥SCC Tarjan,斐波那契堆等(算)法如其名,均为美国计算机科学家,图灵奖得主罗伯特·塔扬(Robert Endre Tarjan)所发明。哎wc罗伯特怎么这么郝啊.jpg
非常巧妙的利用了并查集压缩路径的过程,在压缩的同时利用dfs完成查找,总之非常非常巧妙。
时间复杂度接近O(n).
// Tarjan
#include <iostream>
#include <algorithm>
#include <vector>
#include <utility>
using namespace std;
vector<vector<pair<int, int>>> query;
vector<vector<int>> edg;
vector<int> fa, ans, vis;
void init(int n)
{
fa.resize(n + 3);
for (int i = 1; i <= n; i++)
{
fa[i] = i;
}
}
int find(int x)
{
if (x != fa[x])
{
fa[x] = find(fa[x]);
}
return fa[x];
}
void tarjan(int x)
{
vis[x] = 1;
for (auto& i : edg[x])
{
if (!vis[i])
{
tarjan(i);
fa[i] = x;
}
}
for (auto& i : query[x])
{
if (vis[i.first])
{
ans[i.second] = find(i.first);
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m, s, x, y;
cin >> n >> m >> s;
init(n);
edg.resize(n + 3);
ans.resize(n + 3);
vis.resize(n + 3, 0);
query.resize(n + 3);
for (int i = 1; i < n; i++)
{
cin >> x >> y;
edg[x].emplace_back(y);
edg[y].emplace_back(x);
}
for (int i = 1; i <= m; i++)
{
cin >> x >> y;
query[x].emplace_back(make_pair(y, i));
query[y].emplace_back(make_pair(x, i));
}
tarjan(s);
for (int i = 1; i <= m; i++)
{
cout << ans[i] << endl;
}
return 0;
}

目前有关LCA就这些内容。希望我以后遗忘可以慢一点。。。同时如果这也能帮到你,我很荣幸。真的。。
版权声明:
作者:Toms Project
链接:https://blog.projectoms.com/pages/1510.html
来源:Toms Project 官方博客
文章版权归作者所有,未经允许请勿转载。


共有 0 条评论