tarjian算法
LCA: LCA(Least Common Ancestor),顾名思义,是指在一棵树中,距离两个点最近的两者的公共节点。也就是说,在两个点通往根的道路上,肯定会有公共的节点,我们就是要求找到公共的节点中,深度尽量深的点。还可以表示成另一种说法,就是如果把树看成是一个图,这找到这两个点中的最短距离。
LCA算法有在线算法也有离线算法,所谓的在线算法就是实时性的,而离线算法则是要求一次性读入所有的请求,然后在统一得处理。而在处理的过程中不一定是按照请求的输入顺序来处理的。说不定后输入的请求在算法的执行过程中是被先处理的。
tarjan算法。这个算法是基于并查集和DFS的。离线算法。
现在我们来观察正在处理与x结点关联的询问时并查集的情况。由于一个结点处理完毕后,它就被归到其父结点所在的集合,所以在已经处理过的结点中(包括 x本身),x结点本身构成了与x的LCA是x的集合,x结点的父结点及以x的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[x]的集合,x结点的父结点的父结点及以x的父结点的所有已处理的兄弟结点为根的子树构成了与x的LCA是father[father[x]]的集合……(上面这几句话如果看着别扭,就分析一下句子成分,也可参照右面的图)假设有一个询问(x,y)(y是已处理的结点),在并查集中查到y所属集合的根是z,那么z 就是x和y的LCA,x到y的路径长度就是 lv[x]+lv[y]-lv[z]*2 。累加所有经过的路径长度就得到答案。 现在还有一个问题:上面提到的询问(x,y)中,y是已处理过的结点。那么,如果y尚未处理怎么办?其实很简单,只要在询问列表中加入两个询问(x, y)、(y,x),那么就可以保证这两个询问有且仅有一个被处理了(暂时无法处理的那个就pass掉)。而形如(x,x)的询问则根本不必存储。 如果在并查集的实现中使用路径压缩等优化措施,一次查询的复杂度将可以认为是常数级的,整个算法也就是线性的了。
Tarjan作为离线off-line算法,在程序开始前,需要将所有等待询问的节点对提前存储,然后程序从树根开始执行TarjanLCA()。假如有如下一棵多叉树
根据TarjanLCA的实现算法可以看出,只有当某一棵子树全部遍历处理完成后,才将该子树的根节点标记为黑色(初始化是白色),假设程序按上面的树形结构进行遍历,首先从节点1开始,然后递归处理根为2的子树,当子树2处理完毕后,节点2, 5, 6均为黑色;接着要回溯处理3子树,首先被染黑的是节点7(因为节点7作为叶子不用深搜,直接处理),接着节点7就会查看所有询问(7, x)的节点对,假如存在(7, 5),因为节点5已经被染黑,所以就可以断定(7, 5)的最近公共祖先就是find(5).ancestor,即节点1(因为2子树处理完毕后,子树2和节点1进行了union,find(5)返回了合并后的树的根1,此时树根的ancestor的值就是1)。 有人会问如果没有(7, 5),而是有(5, 7)询问对怎么处理呢?我们可以在程序初始化的时候做个技巧,将询问对(a, b)和(b, a)全部存储,这样就能保证完整性。
如果要按顺序输出,在输入询问时打一个标记,假设开一个数组 f[][] , f[x][] = "顺序",
que[x][i]=y 是询问x,y,与x有关的第 i 个数是y,f[x][i] = "顺序" ,与x有关的第 i 个询问是的几个顺序,所以就可以表示输出的顺序了。
模板
1 #include<iostream>
2 #include<cstdio>
3 #include<algorithm>
4 #include<vector>
5 #include<cstring>
6 using namespace std;
7
8 const int N = 10010;
9
10 vector<int>node[N]; //相连的边
11 vector<int>que[N]; //询问
12 int n,m,x,y;
13 int far[N],ance[N]; //祖先,集合
14 int rank[N],ru[N]; //树的秩,入度
15 bool vis[N]; //判断是否访问过
16
17 void init()
18 {
19 for(int i=0;i<=n;i++)
20 {
21 node[i].clear();
22 que[i].clear();
23 far[i] = i;
24 rank[i] = 1;
25 }
26 }
27
28 int find(int x)
29 {
30 return far[x]==x?x:far[x]=find(far[x]);
31 }
32
33 void unio(int a,int b)
34 {
35 a=find(a);
36 b=find(b);
37 if(a==b) return ;
38 if(rank[a]<=rank[b])
39 {
40 far[a] = b;
41 rank[b] += rank[a];
42 }
43 else
44 {
45 far[b] = a;
46 rank[a] += rank[b];
47 }
48 }
49
50 void LCA(int root)
51 {
52 ance[root] = root; //首先自成一个集合
53 for(int i=0;i<node[root].size();i++)
54 {
55 LCA(node[root][i]); //递归子树
56 unio(root,node[root][i]); //将子树节点与根节点root的集合合并
57 ance[find(root)] = root; //合并后的集合的祖先为root
58 }
59 vis[root] = true;
60 for(int i=0;i<que[root].size();i++)
61 {
62 if(vis[que[root][i]]) //如果另一节点已经访问过
63 printf("%d和%d的最近公共祖先:%d\n", root,que[root][i],ance[find(que[root][i])]);
64 }
65 }
66
67 int main()
68 {
69 scanf("%d%d",&n,&m);
70 init(); //调试了好久,原来这里的初始化要放在输入n下面
71 for(int i=1;i<n;i++)
72 {
73 scanf("%d%d",&x,&y); //x->y有一条边
74 node[x].push_back(y);
75 //node[y].push_back(x);
76 ru[y]++;
77 }
78 for(int i=1;i<=m;++i)
79 {
80 scanf("%d%d",&x,&y);
81 que[x].push_back(y);
82 que[y].push_back(x);
83 }
84 for(int i=0;i<=n;++i)
85 if(ru[i]==0) LCA(i); //寻找根节点
86 return 0;
87 }
来源:https://www.cnblogs.com/mjtcn/p/6852646.html