LCA 最近公共祖先

xiaoxiao2021-02-28  95

本篇博客总结一下LCA问题及其离线算法tarjan,并辅以模板以助记忆。

所谓LCA问题就是查询两个节点的最近公共祖先,tarjan算法利用深度优先检索和并查集来解决这一问题,对于所有的查询,可在一次dfs的过程中求解。

具体的tarjan算法看这篇博客,这位博主写得已经非常完备了:

最近公共祖先LCA(Tarjan算法)的思考和算法实现

看完了tarjan的工作过程再看一下它的代码实现: poj 1330  Nearest Common Ancestors 我用这道题来展示一下LCA的模板:首先找到树的根,然后套用tarjan算法。至于为什么tarjan算法可行,上面那篇博文里没讲,我这里在注释中给出了粗略的证明,简单地说就是 刚刚好访问两个节点时,这两个节点的LCA必定没有 在DFS中回溯,正是因为没有回溯,使得find(v)可以定位到它。 #include <iostream> #include <vector> #include <fstream> #define M 100009 using namespace std; int f[M]; int root; int degree_in[M]; bool vis[M]; vector<int> tree[M]; vector<int> query[M]; int find(int x) { return x == f[x] ? f[x] : f[x] = find(f[x]); //并查集的路径压缩操作 } void tarjan(int u) { for (int i = 0; i < tree[u].size(); i++)//dfs { int v = tree[u][i]; tarjan(v); f[v] = u; //v搜索完,将v合并到它父亲的集合u中 } vis[u] = 1; for (int i = 0; i < query[u].size(); i++) { int v = query[u][i]; if (vis[v]) //v被搜索过 { cout << find(v) << endl; //寻找v所在的集合,即为最近公共祖先 } /*证明:设lca为u和v的最近公共祖先,v和u必然分别在lca的两个不同的子树中(否则必然能找到更接近u、v的LCA),根据dfs性质, u还没有完成dfs,因此lca也没有完成dfs,f[lca]=lca依然成立,而v所在的子树搜索已经完成,因此其f已经全部更新,通过find函数一级级向上找, 最终由于f[lca] = lca而停止,证毕。*/ } } void init(int n) { for (int i = 0; i <= n; i++) { vis[i] = false; tree[i].clear(); query[i].clear(); degree_in[i] = 0; f[i] = i; } } int main() { //fstream cin("test.txt"); int k; cin >> k; while (k--) { int n; cin >> n; init(n); for (int i = 0; i < n - 1; i++) { int a, b; cin >> a >> b; tree[a].push_back(b); degree_in[b]++; } for (int i = 1; i <= n; i++) { if (degree_in[i] == 0) root = i; } int a, b; cin >> a >> b; query[a].push_back(b); query[b].push_back(a); tarjan(root); } //system("pause"); return 0; } poj1986  Distance Queries 这道题复杂一点,其本身是一个无向带权图,可以当成树来做,不是让你直接求LCA,而是要两个点之前的距离。

用dis[]来表示到root的距离,则a、b之间的距离就等于dis[ a ] + dis[ b ] — 2*dis[ LCA(a,b) ]

#include <iostream> #include <fstream> #include <vector> #include <cstdio> using namespace std; struct node { int num, dis; //dis(index for query) node(int a, int b) :num(a), dis(b) {}; }; vector<node> tree[40005], query[40005]; int ans[10005]; int dis[40005]; int father[40005]; bool vis[40005]; int find(int x) { if (x != father[x]) father[x] = find(father[x]); return father[x]; } void tarjan(int u, int len) { dis[u] = len; father[u] = u; vis[u] = 1; for (int i = 0; i < query[u].size(); i++) { int v = query[u][i].num; if (vis[v]) { int index = query[u][i].dis; int lca = find(v); ans[index] = dis[u] + dis[v] - 2 * dis[lca]; } } for (int i = 0; i < tree[u].size(); i++) { if (!vis[tree[u][i].num]) { int v = tree[u][i].num; int dis = tree[u][i].dis; tarjan(v, len + dis); father[v] = u; } } } int main() { //fstream cin("test.txt"); int n, m; scanf("%d%d", &n, &m); int a, b, dis; char ch; for (int i = 0; i < m; i++) { scanf("%d%d%d %c", &a, &b, &dis, &ch); tree[a].push_back(node(b, dis)); tree[b].push_back(node(a, dis)); } int q; scanf("%d", &q); for (int i = 0; i < q; i++) { scanf("%d%d", &a, &b); query[a].push_back(node(b, i)); query[b].push_back(node(a, i)); } tarjan(1, 0); for (int i = 0; i < q; i++) printf("%d\n", ans[i]); //system("pause"); return 0; }

转载请注明原文地址: https://www.6miu.com/read-50620.html

最新回复(0)