1.前言
首先我们介绍的算法是LCA问题中的离线算法-Tarjan算法,该算法采用DFS+并查集,再看此算法之前首先你得知道并查集(尽管我相信你如果知道这个的话肯定是知道并查集的),Tarjan算法的优点在于相对稳定,时间复杂度也比较居中,也很容易理解(个人认为)。
2.思想
下面详细介绍一下Tarjan算法的思想:
1.任选一个点为根节点,从根节点开始。
2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。
3.若是v还有子节点,返回2,否则下一步。
4.合并v到u上。
5.寻找与当前点u有询问关系的点v。
6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。
从上面步骤可以看出,Tarjan算法要用到并查集。这里,我们使用链式前向星来存储边和询问情况。
3.算法实现
不妨以洛谷P3379:LCA模板题为例题
1 #include<cstdio> 2 #include<cstring> 3 4 const int maxn = 5e5 + 10,maxm = 5e5 + 10; 5 int fa[maxn],fir[maxn],frt[maxn],ans[maxm],n,m,s,tot,id; 6 bool flag[maxn]; 7 //存边 8 struct Edge{ 9 int v,next; 10 }e[maxn << 1]; 11 //存询问 12 struct Ques{ 13 int v,next,qid; 14 }q[maxm << 1]; 15 //加边 16 void addEdge(int u,int v){ 17 e[tot] = (Edge){v,fir[u]}; 18 fir[u] = tot++; 19 e[tot] = (Edge){u,fir[v]}; 20 fir[v] = tot++; 21 } 22 //加询问 23 void addQues(int u,int v,int qid){ 24 q[id] = (Ques){v,frt[u],qid}; 25 frt[u] = id++; 26 q[id] = (Ques){u,frt[v],qid}; 27 frt[v] = id++; 28 } 29 //读入优化 30 inline int read(){ 31 int sum = 0; 32 char ch = getchar(); 33 while(ch < '0' || ch > '9') ch = getchar(); 34 while(ch >= '0' && ch <= '9'){sum = sum * 10 + ch - '0'; ch = getchar();} 35 return sum; 36 } 37 //输出优化 38 void write(int x){ 39 if(x / 10) write(x / 10); 40 putchar(x % 10 + '0'); 41 } 42 //寻找父亲 43 int find(int x){ 44 if(fa[x] != x) fa[x] = find(fa[x]); 45 return fa[x]; 46 } 47 //合并 48 void unionn(int x,int y){ 49 int fx = find(x),fy = find(y); 50 fa[fx] = fy; 51 } 52 //离线操作(u表示当前点,fa表示该点的前驱点) 53 void dfs(int u,int fa){ 54 flag[u] = true;//标记已访问 55 //遍历所有与u相连的点 56 for(int i = fir[u];i != -1;i = e[i].next){ 57 int v = e[i].v; 58 //如果v不是u的前驱 59 if(v != fa){ 60 dfs(v,u);//遍历下一层 61 //寻找是否有询问关系 62 for(int j = frt[v];j != -1;j = q[j].next){ 63 int k = q[j].v; 64 if(flag[k]) 65 ans[q[j].qid] = find(k); 66 } 67 unionn(v,u);//连接 68 } 69 } 70 } 71 //主函数 72 int main(){ 73 n = read(),m = read(),s = read(); 74 memset(fir,-1,sizeof(fir)); 75 memset(frt,-1,sizeof(frt)); 76 for(int i = 1;i < n;i++){ 77 fa[i] = i; 78 int x = read(),y = read(); 79 addEdge(x,y); 80 } 81 fa[n] = n; 82 for(int i = 1;i <= m;i++){ 83 int x = read(),y = read(); 84 addQues(x,y,i); 85 } 86 dfs(s,-1); 87 //因为这里已经给出根了,直接从根开始 88 for(int i = 1;i <= m;i++,putchar(' ')) write(ans[i]); 89 return 0; 90 }