LCA算法简介:
对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。
LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径。
LCA算法分为离线算法和在线算法
离线算法( off line algorithms),是指基于在执行算法前输入数据已知的基本假设,也就是说,对于一个离线算法,在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果。
在线算法是指它可以以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入。
LCA的离线算法主要指的是基于深度优先搜索的tarjan算法
Tarjan求LCA的基本思路:
Step 1.任选一个点为根节点,从根节点开始。
Step 2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。
Step 3.若是v还有子节点,返回2,否则下一步。
Step 4.合并v到u上。
Step 5.寻找与当前点u有询问关系的点v。
Step 6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。
例题详解:洛谷P3379【模板】最近公共祖先(LCA)https://www.luogu.com.cn/problem/P3379
核心代码如下:
1 int Find(int u){//查找节点 u 的祖先节点
2 if(f[u] == u) return f[u];
3 return f[u] = Find(f[u]);
4 }
5 void Tarjan(int u){
6 vis[u] = 1;//表示节点 u 正在访问中
7 f[u] = u;//先给 f 数组赋上初始值,即为节点本身
8 for(int i = head1[u]; i; i = e1[i].next){
9 int v = e1[i].to;
10 if(!vis[v]){//节点 v 没访问过则访问节点 v
11 Tarjan(v);
12 f[v] = u;//回溯时将 f[v] 更改为其父亲节点
13 }
14 }
15 vis[u] = -1;//标记节点 u 已访问
16 for(int i = head2[u]; i; i = e2[i].next){
17 int v = e2[i].to;
18 if(vis[v] == -1) ans[(i + 1) / 2] = Find(v);//若节点 v 已变黑,则询问 v 的祖先,因为第1条边和第2条边相当于是同一条边,我们这里写成(i + 1)/ 2 将他们的祖先存放于同一数组中
19 }
20 }
在这里我们用一个 f 数组表示节点 u 的父亲节点,vis 数组表示节点 u 的访问情况,若 vis[u] = 1 则表示节点 u 正在访问中,若 vis[u] = -1 则表示节点 u 已访问,若 vis[u] = 初始值0 则表示节点 u 还未访问。具体解释见代码。
下面是完整代码:
1 #include<bits/stdc++.h>
2 using namespace std;
3 const int maxn = 5e5 + 5;
4 struct Edge{
5 int to, next;
6 }e1[maxn << 1], e2[maxn << 1];//左移1表示 * 2
7 int n, root, q;
8 int f[maxn];
9 int head1[maxn], head2[maxn], len1, len2;
10 void Insert(int u, int v, Edge e[], int head[], int &len){
11 e[++len].to = v;
12 e[len].next = head[u];
13 head[u] = len;
14 }
15 void Read(){
16 scanf("%d%d%d", &n, &q, &root);
17 for(int i = 1; i < n; ++i){
18 int u, v;
19 scanf("%d%d", &u, &v);
20 Insert(u, v, e1, head1, len1);
21 Insert(v, u, e1, head1, len1);
22 }
23 for(int i = 1; i <= q; ++i){
24 int u, v;
25 scanf("%d%d", &u, &v);
26 Insert(u, v, e2, head2, len2); //给询问的边建立邻接表关系便于进行询问
27 Insert(v, u, e2, head2, len2);
28 }
29 }
30 int vis[maxn];
31 int ans[maxn];
32 int Find(int u){//查找节点 u 的祖先节点
33 if(f[u] == u) return f[u];
34 return f[u] = Find(f[u]);
35 }
36 void Tarjan(int u){
37 vis[u] = 1;//表示节点 u 正在访问中
38 f[u] = u;//先给 f 数组赋上初始值,即为节点本身
39 for(int i = head1[u]; i; i = e1[i].next){
40 int v = e1[i].to;
41 if(!vis[v]){//节点 v 没访问过则访问节点 v
42 Tarjan(v);
43 f[v] = u;//回溯时将 f[v] 更改为其父亲节点
44 }
45 }
46 vis[u] = -1;//标记节点 u 已访问
47 for(int i = head2[u]; i; i = e2[i].next){
48 int v = e2[i].to;
49 if(vis[v] == -1) ans[(i + 1) / 2] = Find(v);//若节点 v 已变黑,则询问 v 的祖先,因为第1条边和第2条边相当于是同一条边,我们这里写成(i + 1)/ 2 将他们的祖先存放于同一数组中
50 }
51 }
52 void Print(int a[], int n){
53 for(int i = 1; i <= n; ++i)
54 printf("%d/n", a[i]);
55 }
56 int main(){
57 Read();
58 Tarjan(root);//从根节点开始查询
59 Print(ans, q);
60 return 0;
61 }
完结撒花

原创文章,作者:sunnyman218,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/277519.html