Tarjan属于离线做法,即将问题全部储存起来后一起处理一起回答,相比于即问即答的在线做法,tarjan能仅通过一次DFS就能解决所有的LCA问题。

具体很简单,运用了时间戳并查集

visit数组记录某节点是否访问过,用f记录它的father是谁。

先上伪代码

Tarjan(u)//marge和find为并查集合并函数和查找函数
{
    for each(u,v)    //访问所有u子节点v
    {
        Tarjan(v);        //继续往下遍历
        marge(u,v);    //合并v到u上
        标记v被访问过;
    }
    for each(u,e)    //访问所有和u有询问关系的e
    {
        如果e被访问过;
        u,e的最近公共祖先为find(e);
    }
}

从根节点不断往下遍历,回溯的时候将孩子和父亲合并,当一个节点i的所有儿子都遍历完了(包括儿子的儿子等等),就搜寻一遍询问看看能否回答一些,能够回答就回答,不能回答的之后再回答。

至于能不能回答,取决于这个询问涉及到的另一个点是否已经访问过,如果还没访问,则暂时不能回答,如果访问了,则寻找另一个点的father,该father就是它们的最近公共祖先了。

该算法之所以能够找到LCA,是因为一个已经的访问过的点A另一个正在访问的点B的LCA一定是一个还没结束访问的点C(就是还没遍历完孩子,没有和C的父亲合并的点)

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #define N 3000002
 5 using namespace std;
 6 int n,m,num1,num2,ans[N],f[N],u,v,head[N],qhead[N],visit[N];
 7 struct data2{
 8     int next,to,sign;   //链式前向星储存(其实sign可以不用) 
 9 }line[N],qline[N];
10 void add1(int u,int v){
11     num1++;
12     line[num1].next=head[u];
13     line[num1].to=v;
14     line[num1].sign=num1;
15     head[u]=num1;
16     num1++;
17     line[num1].next=head[v];
18     line[num1].to=u;
19     line[num1].sign=num1;
20     head[v]=num1;
21 }
22 void add2(int u,int v){    //询问建立双向的链式前向星一方面方便查询询问,另一方面确保能回答到问题 
23     num2++;
24     qline[num2].next=qhead[u];
25     qline[num2].to=v;
26     qline[num2].sign=num2;
27     qhead[u]=num2;
28     num2++;
29     qline[num2].next=qhead[v];
30     qline[num2].to=u;
31     qline[num2].sign=num2;
32     qhead[v]=num2;
33 }
34 int find(int x){     //找父亲 
35     if (f[x]==x) return x;
36     f[x]=find(f[x]);
37     return f[x];
38 }
39 void tarjan(int u){   //Tarjan求LCA 
40     f[u]=u;    
41     visit[u]=1;    //标记该点访问过 
42     int v=0,e=0;
43     for (int i=head[u];i!=0;i=line[i].next){    //遍历孩子 
44         v=line[i].to;
45         if (!visit[v]) {     //没有访问过的深搜 
46             tarjan(v);
47             f[v]=u;
48         }
49     }
50     for (int i=qhead[u];i!=0;i=qline[i].next){     //遍历完该点的孩子后查询询问 
51         v=qline[i].to;
52         if (visit[v]){      //若该点访问过,则可以知道答案 
53         e=find(v);
54         if (qline[i].sign&1) ans[(qline[i].sign+1)/2]=e;
55         else ans[qline[i].sign/2]=e;
56         }
57     }
58 }
59 int main(){
60     memset(visit,0,sizeof(visit));
61     scanf("%d%d",&n,&m);    //n个点,m个询问
62     num1=0;
63     num2=0;
64     for (int i=1;i<=n-1;i++){    //建图 
65         scanf("%d%d",&u,&v);
66         add1(u,v);
67     }
68     for (int i=1;i<=m;i++){     //建立询问图 
69         scanf("%d%d",&u,&v);
70         add2(u,v);
71     }
72     tarjan(1);  //默认1为根节点 
73     for (int i=1;i<=m;i++)
74      printf("%d\n",ans[i]);
75     return 0;
76 }
Tarjan

相关文章:

  • 2021-09-05
  • 2021-05-27
  • 2021-12-15
  • 2022-02-04
  • 2022-12-23
  • 2021-11-01
  • 2021-07-29
  • 2021-05-10
猜你喜欢
  • 2022-01-15
  • 2022-03-01
  • 2022-12-23
相关资源
相似解决方案